随笔 - 632  文章 - 17  评论 - 54  阅读 - 93万

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;
}
复制代码

 

posted on   飘杨......  阅读(418)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

点击右上角即可分享
微信分享提示