您好,欢迎来到尚车旅游网。
搜索
您的当前位置:首页LINUX下网络即时聊天程序-

LINUX下网络即时聊天程序-

来源:尚车旅游网
基于TCP协议的简易网络聊天程序

一、设计原理:

即时通信(IM)是指能够即时发送和接收互联网消息等的业务。 自1998年面世以来,特别是近几年的迅速发展,即时通信的功能日益丰富,逐渐集成了电子邮件、博客、音乐、电视、游戏和搜索等多种功能。即时通信不再是一个单纯的聊天工具,它已经发展成集交流、资讯、娱乐、搜索、电子商务、办公协作和企业客户服务等为一体的综合化信息平台。本课题实现简单的及时通讯中的聊天服务。 问题定义:

本课题要解决的问题是提供用户自由向另外一个不同的用户发消息的同时接收来自其他用户的消息。 2. 可行性研究:

要实现即时通讯的聊天模块,可以在LINUX下搭建服务器,在提供给用户客户端程序。客户端和服务器之间通过TCP协议实现在线聊天。 需求分析:

(1)为了实现即时通讯的聊天服务,聊天服务器必须能够同时接入多个用户,所以要支持并发,允许同时在线客户端数量至少大于3个。(2)要求服务器能接收多个用户的接入请求同时处理已经建立连接的用户的操作。(3)接收用户发过来的信息。(3)正确转发信息到达正确的用户。(4)提供简单的用户操作指令比如显示实时在线的用户。 (5).来自不同用户的信息的转发的同步控制(6)。给每个用户一个唯一的ID。 4. 总体设计

首先我们应该在设计LINUX平台设计服务器并且C语言编程。在实现并行处理时可以使用多进程也可以使用多线程,多线程可以方便的实现不同连接间简易的通信,系统开销小所以这里选用它。在连接的协议选择上,因为传送数据量小,这里选择面向连接可靠传输的TCP协议,相比将套接字嵌入FILE留种,这里使用调用常用的tcp的套接字API(send,recv)读写缓冲区实现连接的方法. 5.详细设计

客户端:首先用户提供服务器IP和端口,然后创建套接字并连接到服务器,连接成功给予操作界面。设计用户登陆接口函数,发送名字用于登陆处理。主进程挂载随时接收用户键盘输入,并调用SEND()函数处理发送和指令操作。创立一个线程用于挂载阻塞的recv();将收到的信息打印。

服务器:服务器创建通用的服务器套接字绑定IP和端口,主进程监听和循环接

受客户端接入请求。接入用户后创建线程传入套接字,并接收用户的登陆。登陆处理后讲该线程绑定一个用户ID,并创建一个线程用于挂载RECV();第一个线程处理要发给本连接绑定用户的转发服务。同时设计在线用户的更新和打印处理。使用互斥锁来解决写入的同步控制问题,在更改全局变量时候锁住,当转发完毕时,解锁。 6.编码和单元测试

编码见代码和流程图。测试没有截图,提列测试出现的部分问题:(1)段错误(核心已转储),原因:字符串处理函数字符串数组与字符串指针的调用问题。改进:数组统一换字符串指针,分配空间。(2)初始值设定不是常量,原因初始化在main()开始前执行。改进:结构体分配在主函数里分配空间。(3)当recv()从缓冲器COPY的数据产生粘包。原因:没有消息边界,解决:每次收发大小固定住。(4)连接关闭处理。解决:设置套接字收发的标志位获取退出原因,友好退出时套接字函数返回0。

二、运行方法说明、运行结果

运行方法:linux下编译服务器gcc server.c die.c -lpthread -o server.out Linux下编译客户端gcc client.c die.c -lpthread -o client.out 服务器运行server.out <端口号/服务名称>

客户端运行client.out <服务器地址><端口号/服务名称>

服务器运行成功后有提示,客户端运行成功并登陆成功后有欢迎界面。 用户可以输入LIST命令来获得在线用户列表。 用户可以输入CLOSE来退出程序。

运行结果:能达到设计要求,运行速度正常。

三、流程图

客户端流程图

开始 创建、设置套接字 Connect(); 创建、设置套接字 Nameland(); Pthread_creat(); 接受键盘输入 是否输入 为”close”

yes no Send();定长数据 执行threadmain() Recv()定长数据 打印信息 Close(sock) 结束

开始 服务器流程图

执行threadmain(); 传入套接字和id 执行threadmain(); 线程指定id 传入套接字 While(1) 用户名登陆处理 线程指定id 创建、设置套接字 Bamd(); Listrren(); Accept() While(1) 创建clientsock Pthread_craet(); Recv()定长数据 Close(serversock) 结束 If(recv!=0 更新用户名列表 判断收到的信Pthread_crreat(); 息根据不同内 容进行封装 While(1) 封装信息,ID=id

If(ID==id) Close(clientsock) 更新用户列表 Send()转发信息 服务器

#include #include #include

#include #include #include #include #include #include \"Practical.h\"

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //定义一个静态锁 char *rmessage;//当前受到来自客户端的源信息 int currentID=6;

char *clientlist[5];//用户组姓名

void *ThreadMain(void *arg); //挂send

void *ThreadMain2(void *arg);//挂阻塞rcvd

typedef struct ThreadArgs {

int clntSock; //线程要处理的连接 int nameID;//用户在用户组的位置 }threadMessage;

int main(int argc, char *argv[]) {

if (argc != 2) // Test for correct number of arguments

DieWithUserMessage(\"Parameter(s)\ char *servPort = argv[1]; // First arg: local port rmessage=(char *)malloc(120); memset(rmessage,0,120);

int servSock = SetupTCPServerSocket(servPort); if (servSock < 0)

DieWithUserMessage(\"SetupTCPServerSocket() failed\ for (;;) { // Run forever

int clntSock = AcceptTCPConnection(servSock);

threadMessage *threadArgs = (threadMessage *) malloc( sizeof(threadMessage)); if (threadArgs == NULL)

DieWithSystemMessage(\"malloc() failed\"); threadArgs->clntSock = clntSock; pthread_t threadID;

int returnValue = pthread_create(&threadID, NULL, ThreadMain, threadArgs); if (returnValue!= 0)

DieWithUserMessage(\"pthread_create() failed\ //printf(\"with thread: %ld\\n\ }

free(rmessage); close(servSock); return 0; }

void *ThreadMain(void *threadArgs) { char *sname=(char *)malloc(20); char rongname[]=\"the name existed\"; char rightname[]=\"land success\"; int i=0;

memset(sname,0,20);

pthread_detach(pthread_self());

int clntSock =((threadMessage *)threadArgs)->clntSock; while(i!=5){//判断用户名是否存在

ssize_t nameRcvd = recv(clntSock, sname,20, 0); if (nameRcvd <=0)

DieWithSystemMessage(\"recv() failed\"); *(sname+nameRcvd)='\\0'; for(i=0;i<5;i++){

if(clientlist[i]!=NULL)

if(strcmp(sname,clientlist[i])==0){

if(send(clntSock,rongname,sizeof(rongname), 0)<=0)

DieWithSystemMessage(\"sendname() failed\"); printf(\"用户名已经存在\\n\"); break; } } }

for(i=0;i<5;i++){//指定用户名在用户组的位置 if(clientlist[i]==NULL)break; }

if(send(clntSock,rightname,sizeof(rightname), 0)<=0)

DieWithSystemMessage(\"sendrightname() failed\"); printf(\"接入用户名:%s\\n\ clientlist[i]=(char *)malloc(20); memset(clientlist[i],0,20); strcpy(clientlist[i],sname);

//将该连接相关信息传给该连接的第二个线程 ((threadMessage *)threadArgs)->nameID=i; pthread_t threadID;

int returnValue= pthread_create(&threadID, NULL, ThreadMain2, threadArgs); if(returnValue!=0)

DieWithUserMessage(\"pthread_create() failed\ while(1){

if(clientlist[i]==NULL)break;

if(i==currentID){//判断是否是给本连接的信息,是就发出去 ssize_t numBytesSent = send(clntSock,rmessage,120, 0); if (numBytesSent < 0)

DieWithSystemMessage(\"send() failed\"); else if (numBytesSent != 120)

DieWithUserMessage(\"send()\ //strcpy(rmessage,\"null\");

currentID=6;//设置标志,防止在此发送

if(pthread_mutex_unlock(&mutex)!=0)//转发完成就解锁 printf(\"unlock failed\"); } }

free(sname); close(clntSock); return (NULL); }

void *ThreadMain2(void *threadArgs) { char *buffer=(char *)malloc(120); memset(buffer,0,120);

char *y,*b; int nameid,j;

pthread_detach(pthread_self());

int clntSock =((threadMessage *)threadArgs)->clntSock; nameid=((threadMessage *)threadArgs)->nameID; free(threadArgs); // Deallocate memory for argument while(1){

ssize_t numBytesRcvd; int totalRecv=0; //getchar();

while(totalRecv<120){

numBytesRcvd = recv(clntSock, buffer+totalRecv,120-totalRecv, 0); if(numBytesRcvd==0)break;//判断客户关闭 if (numBytesRcvd <0)

DieWithSystemMessage(\"recv() failed\"); totalRecv+=numBytesRcvd; }

if(numBytesRcvd==0)break; printf(\"收到:%s\\n\

//问题4:假设锁被线程1用了,同时线程2阻塞在LOCK,这时如果客户在send2次消息过来,线程2下次调用recv会不会把2次send的数据和起来一次放进缓冲区里? if(pthread_mutex_lock(&mutex)!=0) printf(\"lock failed\");

if(strcmp(buffer,\"list\")==0){//编写list命令 char locate[3]={\"\\n1:\

strcpy(rmessage,\"服务器在线用户列表\"); for(j=0;j<5;j++){

if(clientlist[j]!=NULL){ strcat(rmessage,locate); strcat(rmessage,clientlist[j]); locate[1]++; } }

currentID=nameid; continue; }

//解析受到的信息,并编码要传递的信息 b=buffer;

y=strsep(&b,\":\");

for(j=0;j<5;j++){//对话时无效用户名处理 if(clientlist[j]!=NULL)

if(strcmp(y,clientlist[j])==0)break; }

if(j==5){

currentID=nameid;

strcpy(rmessage,\"您要对话的用户不存在\"); }

else{//加入发送者名字 currentID=j;

strcpy(rmessage,clientlist[nameid]); strcat(rmessage,\":\"); strcat(rmessage,b);

printf(\"to:%s\\n\ }

}

//处理客户退出并通知上一层线程

printf(\"用户%s已经退出\\n\问题:一开始没办法及时打印会等下次“收到”才打印?后面测试又可以?

free(clientlist[nameid]); clientlist[nameid]=NULL; free(buffer); return (NULL); }

客户端

#include #include #include #include #include #include #include #include #include \"Practical.h\"

void *ThreadMain(void *arg); //用来挂阻塞的rcvd struct ThreadArgs {

int clntSock; //传递给线程要处理的套接字 };

int main(int argc, char *argv[]) {

if (argc!=3)

DieWithUserMessage(\"Parameter(s)\

\" []\"); char *server = argv[1]; // First arg: server address/name char *echoString=(char *)malloc(120); char *service = argv[2];

int sock = SetupTCPClientSocket(server, service);//创建套接字并connect if (sock < 0)

DieWithUserMessage(\"SetupTCPClientSocket() failed\ nameland(sock);

struct ThreadArgs *threadArgs=(struct ThreadArgs*)malloc(sizeof(struct ThreadArgs)); if(threadArgs ==NULL)

DieWithSystemMessage(\"malloc() failed\"); threadArgs->clntSock=sock; pthread_t threadID;//创建线程

int returnValue=pthread_create(&threadID,NULL,ThreadMain,threadArgs); if(returnValue!=0)

DieWithUserMessage(\"pthread_create failed\

printf(\"|----------登陆成功请按此格式聊天(用户名:信息内容)----------|\\n|-------(信息最长100字符/使用list命令获取在线用户列表/close命令退出)--------|\\n\"); //挂send for(;;){

scanf(\"%s\

if(strcmp(echoString,\"close\")==0)break;

ssize_t numBytes = send(sock, echoString, 120, 0); if (numBytes < 0)

DieWithSystemMessage(\"send() failed\");

else if (numBytes != 120)

DieWithUserMessage(\"send()\ //printf(\"发送字节数:%zd\\n\ }

fputs(\"欢迎再次使用\\n\ free(echoString); close(sock); exit(0); }

void *ThreadMain(void *threadArgs){//挂recv char buffer[120]; // I/O buffer pthread_detach(pthread_self());

int csock = ((struct ThreadArgs *) threadArgs)->clntSock; free(threadArgs); while (1) {

int totalRecv=0;

while(totalRecv<120){

ssize_t numBytesRcvd = recv(csock, buffer+totalRecv,120-totalRecv, 0); if (numBytesRcvd <=0)

DieWithSystemMessage(\"recv() failed\"); totalRecv+=numBytesRcvd; }

fputs(\"收到信息来自:\ // Setup to print the echoed string fputs(buffer, stdout); // Print the buffer fputc('\\n', stdout); // Print a final linefeed }

return (NULL); }

//自写的接口 #include #include #include #include #include #include #include #include \"Practical.h\" #include #include

static const int MAXPENDING = 5; // Maximum outstanding connection requests

void DieWithUserMessage(const char *msg, const char *detail) { fputs(msg, stderr); fputs(\": \ fputs(detail, stderr); fputc('\\n', stderr); exit(1); }

void DieWithSystemMessage(const char *msg) { perror(msg); exit(1);

}

int SetupTCPClientSocket(const char *host, const char *service) { // Tell the system what kind(s) of address info we want

struct addrinfo addrCriteria; // Criteria for address match memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure addrCriteria.ai_family = AF_UNSPEC; // v4 or v6 is OK

addrCriteria.ai_socktype = SOCK_STREAM; // Only streaming sockets addrCriteria.ai_protocol = IPPROTO_TCP; // Only TCP protocol

// Get address(es)

struct addrinfo *servAddr; // Holder for returned list of server addrs int rtnVal = getaddrinfo(host, service, &addrCriteria, &servAddr); if (rtnVal != 0)

DieWithUserMessage(\"getaddrinfo() failed\

int sock = -1;

struct addrinfo *addr;

for (addr = servAddr; addr != NULL; addr = addr->ai_next) { // Create a reliable, stream socket using TCP

sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (sock < 0)

continue; // Socket creation failed; try next address

// Establish the connection to the echo server

if (connect(sock, addr->ai_addr, addr->ai_addrlen) == 0)

break; // Socket connection succeeded; break and return socket

close(sock); // Socket connection failed; try next address sock = -1; }

freeaddrinfo(servAddr); // Free addrinfo allocated in getaddrinfo() return sock; }

void PrintSocketAddress(const struct sockaddr *address, FILE *stream) { // Test for address and stream

if (address == NULL || stream == NULL) return;

void *numericAddress; // Pointer to binary address // Buffer to contain result (IPv6 sufficient to hold IPv4) char addrBuffer[INET6_ADDRSTRLEN]; in_port_t port; // Port to print

// Set pointer to address based on address family switch (address->sa_family) { case AF_INET:

numericAddress = &((struct sockaddr_in *) address)->sin_addr; port = ntohs(((struct sockaddr_in *) address)->sin_port); break;

case AF_INET6:

numericAddress = &((struct sockaddr_in6 *) address)->sin6_addr; port = ntohs(((struct sockaddr_in6 *) address)->sin6_port); break; default:

fputs(\"[unknown type]\ // Unhandled type return; }

// Convert binary to printable address

if (inet_ntop(address->sa_family, numericAddress, addrBuffer, sizeof(addrBuffer)) == NULL)

fputs(\"[invalid address]\ else {

fprintf(stream, \"%s\

if (port != 0) // Zero not valid in any socket addr fprintf(stream, \"-%u\ } }

int SetupTCPServerSocket(const char *service) { // Construct the server address structure

struct addrinfo addrCriteria; // Criteria for address match memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure

addrCriteria.ai_family = AF_UNSPEC; // Any address family

addrCriteria.ai_flags = AI_PASSIVE; // Accept on any address/port addrCriteria.ai_socktype = SOCK_STREAM; // Only stream sockets addrCriteria.ai_protocol = IPPROTO_TCP; // Only TCP protocol

struct addrinfo *servAddr; // List of server addresses

int rtnVal = getaddrinfo(NULL, service, &addrCriteria, &servAddr); if (rtnVal != 0)

DieWithUserMessage(\"getaddrinfo() failed\

int servSock = -1; struct addrinfo *addr;

for ( addr= servAddr; addr != NULL; addr = addr->ai_next) { // Create a TCP socket

servSock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (servSock < 0)

continue; // Socket creation failed; try next address

// Bind to the local address and set socket to listen

if ((bind(servSock, addr->ai_addr, addr->ai_addrlen) == 0) && (listen(servSock, MAXPENDING) == 0)) { // Print local address of socket struct sockaddr_storage localAddr; socklen_t addrSize = sizeof(localAddr);

if (getsockname(servSock, (struct sockaddr *) &localAddr, &addrSize) < 0) DieWithSystemMessage(\"getsockname() failed\"); fputs(\"Binding to \

PrintSocketAddress((struct sockaddr *) &localAddr, stdout); fputc('\\n', stdout);

break; // Bind and listen successful }

close(servSock); // Close and try again servSock = -1; }

// Free address list allocated by getaddrinfo() freeaddrinfo(servAddr);

return servSock; }

int AcceptTCPConnection(int servSock) {

struct sockaddr_storage clntAddr; // Client address

// Set length of client address structure (in-out parameter) socklen_t clntAddrLen = sizeof(clntAddr);

// Wait for a client to connect

int clntSock = accept(servSock, (struct sockaddr *) &clntAddr, &clntAddrLen); if (clntSock < 0)

DieWithSystemMessage(\"accept() failed\");

// clntSock is connected to a client!

fputs(\"Handling client \

PrintSocketAddress((struct sockaddr *) &clntAddr, stdout); fputc('\\n', stdout);

return clntSock; }

//登陆名字检测处理 void nameland(int sock){

char *namemessage=(char *)malloc(20); while(1){

printf(\"请输入名字:\"); scanf(\"%s\

size_t namemessageLen = strlen(namemessage); // Determine input length ssize_t numBytes = send(sock, namemessage, namemessageLen, 0); if (numBytes < 0)

DieWithSystemMessage(\"send() failed\"); else if (numBytes != namemessageLen)

DieWithUserMessage(\"send()\//接收服务器来的登陆确认消息

numBytes=recv(sock,namemessage,20,0); if(numBytes<=0)

DieWithSystemMessage(\"recvnamemessage() failed\"); // *(namemessage+numBytes)='\\0';

if(strcmp(namemessage,\"the name existed\")!=0)break;//名字对了就跳出循环 else

printf(\"用户名已存在,重试\"); }

printf(\"%s\\n\}

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- sceh.cn 版权所有 湘ICP备2023017654号-4

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务