服务器端代码:多线程聊天服务器实现
服务器端代码:\n#include "myhead.h"\n\nstruct thread_pool *pool=NULL;//线程池结构体指针\npclient_connfd cli=NULL;//已连接套接字数组结构体指针\npclient_info head=NULL;//客户端信息链表结构体指针\nint totaluser;//总在线用户数\n\n//任务函数\nvoid *my_task(void *arg)\n{\n\tpthread_detach(pthread_self());//设置线程结束后自动回收资源\n\n\t// printf("123\n");\n\tchar buf[100];//接收缓冲区\n\tint ret=0;//是否接收到数据标志位\n\tchar *divide=":";//分隔符,分开收到数据的名字和内容\n\n\t// printf("000\n");\n\tpclient_info m = (pclient_info)arg;\n\n\t// printf("m->connfd:%d\n",m->connfd);\n\t// printf("m->client_name:%s\n",m->client_name);\n\t// printf("111\n");\n\twhile(1)\n\t{\n\t //连接成功,开始接收数据\n\t bzero(buf,sizeof(buf));\n\t ret=recv(m->connfd,buf,sizeof(buf),0);//大于0说明接收到了数据\n\t // if(ret==-1)//5s内无数据\n\t // {\n\t // printf("已超时\n");\n\t // }\n\t // printf("ret:%d\n",ret);\n\t if(ret>0)//5s内有数据\n\t {\n\t printf("%s\n",buf);//打印收到的内容\n\t \n\t //将收到的信息转发给所有客户端\n\t for(pclient_info h=head->next;h!=NULL;h=h->next)//将已经存入的用户名发送给用户端,来确保不会同名\n\t {\n\t send(h->connfd,buf,strlen(buf),0);\n\t }\n\t \n\t //char *p=strtok(buf,divide);//以冒号分割,得到前面的名字\n\t //p=strtok(NULL,buf);//获取冒号后的发送内容\n\t char p= strtok(buf, divide);\n\t \n\t / 继续获取其他的子字符串 */\n\t while( p != NULL ) \n\t {\n\t p = strtok(NULL, divide);\n\t \n\t if(p!=NULL&&strncmp(p,"quit",strlen(p))==0)//客户端输入quit说明该客户端想要下线\n\t {\n\t if(strlen(p)==4)\n\t {\n\t printf("%s已下线\n",m->client_name);\n\t printf("(当前在线用户数:%d)\n", --totaluser);\n\t close(m->connfd);\n\t delete_clientnode(head,m->client_IP);//从链表中删除下线的客户端\n\t }\n\t }\n\t }\n\t // printf( "%s\n", p );\n\t // printf("123\n");\n\t // if(strncmp(p,"quit",strlen(p))==0)//客户端输入quit说明该客户端想要下线\n\t // {\n\t // if(strlen(p)==4)\n\t // {\n\t // printf("%s已下线\n",m->client_name);\n\t // printf("(当前在线用户数:%d)\n", --totaluser);\n\t // delete_clientnode(head,m->client_IP);//从链表中删除下线的客户端\n\t // close(m->connfd);\n\t // }\n\t // }\n\t // printf("222\n");\n\t }\n\t}\n}\n\nint main(int argc,char *argv[])\n{\n\t//初始化线程池\n\tpool = malloc(sizeof(thread_pool));\n\tinit_pool(pool,0);//初始化线程池\n\n\t//初始化已连接套接字数组\n\tcli=init_connfdaddr();\n\t\n\t//初始化客户端结构体链表\n\thead=init_clientlist();\n\n\t//创建套接字\n\tint socket_fd=socket(AF_INET,SOCK_STREAM,0);\n\tprintf("socket_fd:%d\n",socket_fd);\n\n\t//绑定\n\tstruct sockaddr_in server_addr;\n\tsocklen_t len=sizeof(server_addr);//结构体长度\n\tbzero(&server_addr,len);\n\n\tserver_addr.sin_family=AF_INET;//地址族\n\tserver_addr.sin_port=htons(atoi(argv[1]));//端口号\n\tserver_addr.sin_addr.s_addr=htonl(INADDR_ANY);//IP地址\n\tbind(socket_fd,(struct sockaddr *)&server_addr,len);\n\n\t//设置监听套接字\n\tlisten(socket_fd,60);//可同时监听60个客户端\n\n\t//添加非阻塞属性(给socket_fd)\n\tint state;\n\tstate=fcntl(socket_fd,F_GETFL);\n\tstate|=O_NONBLOCK;//在原来的基础上再添加一个非阻塞属性\n\tfcntl(socket_fd,F_SETFL,state);\n\n\t\n\tstruct sockaddr_in client_addr;//客户端结构体\n\tbzero(&client_addr,len);\n\n\tint tmp;//是否成功添加到数组中标志位\n\tchar buf_name[50];//客户端用户名\n\n\t//接收数据\n\twhile(1)\n\t{\n\t bzero(buf_name,sizeof(buf_name));\n\t //等待客户端连接\n\t int connfd=accept(socket_fd,(struct sockaddr *)&client_addr,&len);//一直阻塞直到有客户端连入\n\t // printf("connfd:%d\n",connfd);\n\t if(connfd>0)//表示有客户端连入\n\t {\n\t // //设置非阻塞属性\n\t // state=fcntl(connfd,F_GETFL);\n\t // state|=O_NONBLOCK;\n\t // fcntl(connfd,F_SETFL,state);\n\t \n\t //设置套接字属性为超时接收(给connfd)\n\t // struct timeval v;\n\t // v.tv_sec=10;//设置每次5秒接收时间\n\t // v.tv_usec=0;\n\t \n\t // setsockopt(connfd,SOL_SOCKET,SO_RCVTIMEO,&v,sizeof(v));//设置套接字超时接收属性\n\t \n\t if(totaluser>=60)//为-1表示数组满了,加入失败\n\t {\n\t printf("可连入服务器端的客户端数量已满,无法连接\n");\n\t close(connfd);\n\t continue;\n\t }\n\t \n\t recv(connfd,buf_name,sizeof(buf_name),0);//接收客户端的用户名\n\t \n\t // for(plient_info h=head->next;h!=NULL;h=h->next)//将已经存入的用户名发送给用户端,来确保不会同名\n\t // {\n\t // send(connfd,h->client_name,strlen(h->client_name),0);\n\t // recv(connfd,buf_name,sizeof(buf_name),0);//接收客户端的用户名\n\t // }\n\t \n\t printf("connfd:%d\n",connfd);\n\t printf("新连入的用户名为:%s\n",buf_name);\n\t printf("新连入的IP为:%s\n",inet_ntoa(client_addr.sin_addr));\n\t printf("(当前在线用户数:%d)\n", ++totaluser);\n\t \n\t if(find_nameclientnode(head,buf_name)==NULL&&find_IPclientnode(head,inet_ntoa(client_addr.sin_addr))==NULL)//等于NULL说明链表中没有存入该客户端(按名字)\n\t {\n\t new_clientnode(head,buf_name,inet_ntoa(client_addr.sin_addr),connfd);//将该客户端的名字与IP号插入到链表中\n\t print_clientlist(head);\n\t }\n\t \n\t \n\t pclient_info p=find_IPclientnode(head,inet_ntoa(client_addr.sin_addr));\n\t \n\t \n\t //新客户端连入,开辟一条新线程\n\t add_thread(pool,1);\n\t add_task(pool,my_task,(void *)p);\n\t }\n\t}\n\t\n\t//关闭套接字\n\tclose(socket_fd);\n\n\treturn 0;\n}\n\n客户端代码:\n#include "myhead.h"\n\nint main(int argc,char *argv[])\n{\n\n\t//创建套接字\n\tint socket_fd=socket(AF_INET,SOCK_STREAM,0);\n\tprintf("sockfd = %d\n",socket_fd);\n\n\n\t//连接服务器端\n\tstruct sockaddr_in server_addr;\n\tsocklen_t len=sizeof(server_addr);\n\tbzero(&server_addr,len);\n\tserver_addr.sin_family=AF_INET;//地址族\n\tserver_addr.sin_port=htons(atoi(argv[2]));//服务器端端口号\n\tinet_pton(AF_INET,argv[1],&server_addr.sin_addr);//服务器端ip地址\n\n\tconnect(socket_fd,(struct sockaddr *)&server_addr,len);//套接字连接客户端\n\n\t//输入客户端用户名\n\tchar bufname[50];//客户端名字\n\tbzero(bufname,sizeof(bufname));\n\tprintf("请输入用户名:");\n\tscanf("%s",bufname);\n\tsend(socket_fd,bufname,strlen(bufname),0);\n\n\t// char bufback[50];\n\n\t// while(strncmp(bufname,bufback,strlen(bufname)!=0))\n\t// {\n\t// bzero(bufback,sizeof(bufback));\n\t// recv(socket_fd,bufback,sizeof(bufback),0);//接收客户端的用户名\n\t// if(strncmp(bufname,bufback,strlen(bufname)==0))\n\t// {\n\t// printf("用户名已存在,请重新输入\n");\n\t// printf("请输入用户名:");\n\t// scanf("%s",bufname);\n\t// send(socket_fd,bufname,strlen(bufname),0);\n\t// }\n\t// }\n\n\tchar buf[50];//发送缓冲区\n\tchar buf1[100];//拼接字符串后存入的数组\n\tchar bufall[100];//群聊接收缓冲区\n\tint ret1=0;//客户端是否接收成功标志位\n\n\twhile(1)\n\t{\n\t bzero(buf,sizeof(buf));\n\t bzero(buf1,sizeof(buf1));\n\t bzero(bufall,sizeof(bufall));\n\t printf("请输入要送发的内容:");\n\t scanf("%s",buf);\n\t sprintf(buf1,"%s:%s",bufname,buf);\n\t // sprintf(buf1,"张三:%s",buf);\n\t send(socket_fd,buf1,strlen(buf1),0);\n\t ret1=recv(socket_fd,bufall,sizeof(bufall),0);\n\t printf("ret1:%d\n",ret1);\n\t if(ret1>0)\n\t {\n\t printf("%s\n",bufall);\n\t }\n\t printf("strlenbufall:%ld\n",strlen(bufall));\n\t if(strncmp(buf,"quit",strlen(buf))==0)\n\t {\n\t if(strlen(buf)==4)\n\t {\n\t break;\n\t }\n\t }\n\t}\n\t\n\t//关闭套接字\n\tclose(socket_fd);\n\n\treturn 0;\n}\n\n为什么上述代码中,客户端收不到服务器端发送给自己的消息内容:在服务器端的代码中,发送给所有客户端的消息是通过以下代码实现的:\n\nc\nfor(pclient_info h=head->next;h!=NULL;h=h->next)//将已经存入的用户名发送给用户端,来确保不会同名\n{\n\tsend(h->connfd,buf,strlen(buf),0);\n}\n\n\n这段代码是将消息发送给所有已连接的客户端,而不是只发送给特定的客户端。所以客户端无法接收到服务器端发送给自己的消息。\n\n要解决这个问题,可以在服务器端的代码中添加一个判断,只将消息发送给特定的客户端。可以通过比较客户端的用户名来确定是否为目标客户端。\n\nc\n//将收到的信息转发给特定的客户端\nfor(pclient_info h=head->next;h!=NULL;h=h->next)\n{\n\tif(strcmp(h->client_name, p) == 0) // 判断是否为目标客户端\n\t{\n\t send(h->connfd,buf,strlen(buf),0);\n\t break;\n\t}\n}\n\n\n在客户端代码中,为了接收服务器端发送的消息,需要使用recv函数接收数据,并打印出来。\n\nc\nchar bufall[100];//群聊接收缓冲区\nint ret1=0;//客户端是否接收成功标志位\n\nwhile(1)\n{\n\t// ...\n\n\tret1=recv(socket_fd,bufall,sizeof(bufall),0);\n\tprintf("ret1:%d\n",ret1);\n\tif(ret1>0)\n\t{\n\t printf("%s\n",bufall);\n\t}\n\n\t// ...\n}\n\n\n注意,recv函数接收数据的返回值是实际接收到的字节数,如果返回值大于0,则表示接收成功。所以需要将sizeof(bufall)作为接收数据的长度传入recv函数。另外,打印接收到的消息前需要判断返回值是否大于0,避免打印出未初始化的数据。\n\n另外,还需要注意的是,服务器端发送给客户端的消息需要在客户端接收之前发送,否则客户端可能无法正确接收到消息。所以在服务器端的代码中,可以将发送消息的代码放在接收消息的代码之前。
原文地址: https://www.cveoy.top/t/topic/jqFF 著作权归作者所有。请勿转载和采集!