C语言实现多线程版聊天服务
一、概述
案例:C+pthread+socket实现多线程聊天服务,要求:服务端可以连接多个客户端,客户端发的消息,服务端再转发给客户端。
实现步骤:
1.创建监听文件描述符socket
2.绑定端口bind
3.监听端口listen
4.接收客户端请求,并在新的线程中执行(pthread+accept)
5.服务端把收到的数据恢复给客户端
二、示例代码
1.封装socket的创建、绑定、监听、接收头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <strings.h> void perr_exit( const char *s) { perror (s); exit (-1); } int Accept( int fd, struct sockaddr *sa, socklen_t *salenptr) { int n; again: if ((n = accept(fd, sa, salenptr)) < 0) { if (( errno == ECONNABORTED) || ( errno == EINTR)) goto again; else perr_exit( "accept error" ); } return n; } int Bind( int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = bind(fd, sa, salen)) < 0) perr_exit( "bind error" ); return n; } int Connect( int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = connect(fd, sa, salen)) < 0) perr_exit( "connect error" ); return n; } int Listen( int fd, int backlog) { int n; if ((n = listen(fd, backlog)) < 0) perr_exit( "listen error" ); return n; } int Socket( int family, int type, int protocol) { int n; if ((n = socket(family, type, protocol)) < 0) perr_exit( "socket error" ); return n; } ssize_t Read( int fd, void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = read(fd, ptr, nbytes)) == -1) { if ( errno == EINTR) goto again; else return -1; } return n; } ssize_t Write( int fd, const void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = write(fd, ptr, nbytes)) == -1) { if ( errno == EINTR) goto again; else return -1; } return n; } int Close( int fd) { int n; if ((n = close(fd)) == -1) perr_exit( "close error" ); return n; } /*参三: 应该读取的字节数*/ ssize_t Readn( int fd, void *vptr, size_t n) { size_t nleft; //usigned int 剩余未读取的字节数 ssize_t nread; //int 实际读到的字节数 char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if ( errno == EINTR) nread = 0; else return -1; } else if (nread == 0) break ; nleft -= nread; ptr += nread; } return n - nleft; } ssize_t Writen( int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return n; } static ssize_t my_read( int fd, char *ptr) { static int read_cnt; static char *read_ptr; static char read_buf[100]; if (read_cnt <= 0) { again: if ( (read_cnt = read(fd, read_buf, sizeof (read_buf))) < 0) { if ( errno == EINTR) goto again; return -1; } else if (read_cnt == 0) return 0; read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } ssize_t Readline( int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n' ) break ; } else if (rc == 0) { *ptr = 0; return n - 1; } else return -1; } *ptr = 0; return n; } int tcp4bind( short port, const char *IP) { struct sockaddr_in serv_addr; int lfd = Socket(AF_INET,SOCK_STREAM,0); bzero(&serv_addr, sizeof (serv_addr)); if (IP == NULL){ //如果这样使用 0.0.0.0,任意ip将可以连接 serv_addr.sin_addr.s_addr = INADDR_ANY; } else { if (inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){ perror (IP); //转换失败 exit (1); } } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port); Bind(lfd,( struct sockaddr *)&serv_addr, sizeof (serv_addr)); return lfd; } |
2.连天服务端实现代码
//多线程版本的服务器(优化) #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <ctype.h> #include <pthread.h> #include "wrap.h" typedef struct info{ int cfd;//若为-1表示可用,大于0表示已被占用 int idx; pthread_t thread; struct sockaddr_in client; }INFO; INFO thInfo[1024]; //线程执行函数 void * thread_work(void *arg){ INFO *p = (INFO*)arg; printf("idx==[%d]\n",p->idx); char sIP[16]; memset(sIP,0x00,sizeof(sIP)); int n; int cfd = p->cfd; struct sockaddr_in client; memcpy(&client,&(p->client),sizeof(client)); char buf[1024]; while(1){ memset(buf,0x00,sizeof(buf)); //读数据 n = Read(cfd,buf,sizeof(buf)); if(n<0){ printf("read error or client closed,n==[%d]\n",n); Close(cfd); p->cfd = -1;//设置-1表示该位置可用 pthread_exit(NULL); } for(int i=0;i<n;i++){ buf[i] = toupper(buf[i]); } //发送数据 Write(cfd,buf,n); } } void init_thInfo(){ int i=0; for(i=0;i<1024;i++){ thInfo[i].cfd = -1; } } /** * 查找空闲位置 * */ int findIndex(){ int i; for(i=0;i<1024;i++){ if(thInfo[i].cfd ==-1){ break; } } if(i==1024){ return -1; } return i; } int main(){ //创建socket int lfd = Socket(AF_INET,SOCK_STREAM,0); //设置是端口复用 int opt = 1; setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int)); //绑定ip及端口 struct sockaddr_in serv; bzero(&serv,sizeof(serv)); serv.sin_family = AF_INET; serv.sin_port = htons(8888); serv.sin_addr.s_addr = htonl(INADDR_ANY); Bind(lfd,(struct sockaddr *)&serv,sizeof(serv)); //监听 Listen(lfd,128); //初始化 init_thInfo(); int cfd;//通讯文件描述符 int ret; int idx; socklen_t len; pthread_t thread; struct sockaddr_in client; while(1){ len = sizeof(client); //获得一个新的链接 cfd = Accept(lfd,(struct sockaddr *)&client,&len); //创建一个子线程,让子线程处理链接----接收数据和发送数据 //找数组中空闲位置 idx = findIndex(); if(idx==-1){ Close(cfd); continue; } //对空闲位置的元素的成员赋值 thInfo[idx].cfd = cfd; thInfo[idx].idx = idx; memcpy(&thInfo[idx].client,&client,sizeof(client)); //创建子线程,该子线程完成对数据的收发 ret = pthread_create(&thInfo[idx].thread,NULL,thread_work,&thInfo[idx]); if(ret!=0){ printf("create thread error:[%s]\n",strerror(ret)); exit(-1); } //设置子线程分离属性 pthread_detach(thInfo[idx].thread); } Close(lfd); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探