综合案例
基于TCP的聊天室,支持多个用户同时登陆服务器进行聊天。(相当于群)
socket编程,做一对多的通信,必然要用到多线程,保证多个客户端(并行)登陆服务器时同时进行聊天。
cs:客户端服务器
具体实现:客户端:
1 用户需要登陆,登录时需要输入一个昵称,不需要判断昵称是否重复。
2 用户登陆后连接服务器,进入聊天室。
3 用户可以输入聊天信息,也可以收到别人的聊天信息。
4 用户输入一个特殊的单词 代表退出聊天室
服务器端:
1 启动服务器,开放端口。
2 等待客户端的连接,每连接上来一个客户端就启动一个线程。
3 在线程中与客户端进行交互,交互的过程:如果有客户端登陆、退出、提交聊天信息,服
务器都要把这些消息发给所有客户端。
TCP通信步骤:
服务器端:
1 创建socket
2 准备网络通信地址 sockaddr_in
3 绑定socket和网络通信地址 bind
4 设置监听listen()
5 等待客户端连接accept()//可以用返回的描述符标记不同的客户端,参数可以拿到客户端通信地址
客户端:1 创建socket
2 准备网络通信地址(服务器的IP和端口)
3 连接服务器connect() //成功返回0,,失败返回-1
TCP通信相关函数:send/recv
int send(sockfd,buf,len,flags);
flags:表示发送的方式
如果flags置0,则功能和write一样。
int recv(sockfd,buf,len,flags);
recv函数的返回值:
>0 返回接收到的字节数
<0 接收出错
=0 表示TCP的另一端主动断开连接。
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
af:sin_family
src:IP通信地址,即sin_addr
dst:是一个存储结构,将sin_addr从二进制转换成一个字符串存储在dst中
size:是字符串的长度
server.c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> int sockfd;//服务器socket int fds[100];//客户端的socketfd,100个元素,fds[0]~fds[99] int size =100 ;//用来控制进入聊天室的人数为100以内 char* IP = "192.168.10.143"; short PORT = 10222; typedef struct sockaddr SA; void init(){ sockfd = socket(PF_INET,SOCK_STREAM,0); if (sockfd == -1){ perror("创建socket失败"); exit(-1); } struct sockaddr_in addr; addr.sin_family = PF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = inet_addr(IP); if (bind(sockfd,(SA*)&addr,sizeof(addr)) == -1){ perror("绑定失败"); exit(-1); } if (listen(sockfd,100) == -1){ perror("设置监听失败"); exit(-1); } } void SendMsgToAll(char* msg){ int i; for (i = 0;i < size;i++){ if (fds[i] != 0){ printf("sendto%d\n",fds[i]); send(fds[i],msg,strlen(msg),0); } } } void* service_thread(void* p){ int fd = *(int*)p; printf("pthread = %d\n",fd); while(1){ char buf[100] = {}; if (recv(fd,buf,sizeof(buf),0) <= 0){ int i; for (i = 0;i < size;i++){ if (fd == fds[i]){ fds[i] = 0; break; } } printf("退出:fd = %dquit\n",fd); pthread_exit((void*)i); } //把服务器接受到的信息发给所有的客户端 SendMsgToAll(buf); } } void service(){ printf("服务器启动\n"); while(1){ struct sockaddr_in fromaddr; socklen_t len = sizeof(fromaddr); int fd = accept(sockfd,(SA*)&fromaddr,&len); if (fd == -1){ printf("客户端连接出错...\n"); continue; } int i = 0; for (i = 0;i < size;i++){ if (fds[i] == 0){ //记录客户端的socket fds[i] = fd; printf("fd = %d\n",fd); //有客户端连接之后,启动线程给此客户服务 pthread_t tid; pthread_create(&tid,0,service_thread,&fd); break; } if (size == i){ //发送给客户端说聊天室满了 char* str = "对不起,聊天室已经满了!"; send(fd,str,strlen(str),0); close(fd); } } } } int main(){ init(); service(); }
client.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> int sockfd;//客户端socket char* IP = "192.168.10.143";//服务器的IP short PORT = 10222;//服务器服务端口 typedef struct sockaddr SA; char name[30]; void init(){ sockfd = socket(PF_INET,SOCK_STREAM,0); struct sockaddr_in addr; addr.sin_family = PF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = inet_addr(IP); if (connect(sockfd,(SA*)&addr,sizeof(addr)) == -1){ perror("无法连接到服务器"); exit(-1); } printf("客户端启动成功\n"); } void start(){ pthread_t id; void* recv_thread(void*); pthread_create(&id,0,recv_thread,0); char buf2[100] = {}; sprintf(buf2,"%s进入了聊天室",name); send(sockfd,buf2,strlen(buf2),0); while(1){ char buf[100] = {}; scanf("%s",buf); char msg[131] = {}; sprintf(msg,"%s:%s",name,buf); send(sockfd,msg,strlen(msg),0); if (strcmp(buf,"bye") == 0){ memset(buf2,0,sizeof(buf2)); sprintf(buf2,"%s退出了聊天室",name); send(sockfd,buf2,strlen(buf2),0); break; } } close(sockfd); } void* recv_thread(void* p){ while(1){ char buf[100] = {}; if (recv(sockfd,buf,sizeof(buf),0) <= 0){ return; } printf("%s\n",buf); } } int main(){ init(); printf("请输入您的名字:"); scanf("%s",name); start(); return 0; }