18.5.2 多线程并发服务器端的实现
实现多个客户端之间可以交换信息的简单聊天程序
先上结果:
服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
/*
服务端示例中,需要掌握临界区的构成,访问全局变量 clnt_cnt 和数组 clnt_sock_array 的代码将构
成临界区,添加和删除客户端时,变量 clnt_cnt 和数组 clnt_sock_array 将同时发生变化。
因此下列情形会导致数据不一致,从而引发错误:
1. 线程 A 从数组 clnt_sock_array 中删除套接字信息,同时线程 B 读取 clnt_cnt 变量
2. 线程 A 读取变量 clnt_cnt ,同时线程 B 将套接字信息添加到 clnt_sock_array 数组
*/
#define BUF_SIZE 256
#define MAX_CLEN 256
int clnt_cnt = 0;
int clnt_sock_array[MAX_CLEN];
pthread_mutex_t mutex;
void *handle_clnt(void *arg);
void error_handling(char *msg);
void send_msg(char *msg, int len);
int main(int argc, char *argv[])
{
int clnt_sock = 0;
int serv_sock = 0;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = 0;
pthread_t pid;
if (argc != 2)
{
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
//创建互斥锁
pthread_mutex_init(&mutex, NULL);
//创建套接字
serv_sock = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == serv_sock)
{
error_handling("fail to socket!");
}
//初始化IP地址
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1]));
if (-1 == bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)))
{
error_handling("fail to bind");
}
if (-1 == listen(serv_sock, 5))
{
error_handling("fail to bind");
}
while (1)
{
clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
//上锁
pthread_mutex_lock(&mutex);
//将新客户端存入数组
clnt_sock_array[clnt_cnt++] = clnt_sock;
//解锁
pthread_mutex_unlock(&mutex);
//创建线程为新客户端服务,并且把clnt_sock作为参数
pthread_create(&pid, NULL, handle_clnt, &clnt_sock);
//引导线程销毁,不会阻塞
pthread_detach(pid);
//打印客户端的ip地址
printf("Connected client ID:%s\n", inet_ntoa(clnt_addr.sin_addr));
}
close(serv_sock);
return 0;
}
void *handle_clnt(void *arg)
{
int i = 0;
int str_len = 0;
int clnt_sock = *((int *)arg);
char buf_msg[BUF_SIZE];
while ((str_len = read(clnt_sock, buf_msg, sizeof(buf_msg))) != 0)
{
send_msg(buf_msg, str_len);
}
//接受到的信息为0,说明当前客户端已经断开连接
pthread_mutex_lock(&mutex);
//删除没有连接的客户端
for (i=0; i<clnt_cnt; i++)
{
if (clnt_sock == clnt_sock_array[i])
{
while (i++ < clnt_cnt - 1)
{
clnt_sock_array[i] = clnt_sock_array[i + 1];
}
break;
}
}
clnt_cnt--;
pthread_mutex_unlock(&mutex);
close(clnt_sock);
return NULL;
}
//向所有客户端全发送信息
void send_msg(char *msg, int len)
{
int i;
pthread_mutex_lock(&mutex);
for (i=0; i<clnt_cnt; i++)
{
write(clnt_sock_array[i], msg, len);
}
pthread_mutex_unlock(&mutex);
}
void error_handling(char *msg)
{
fputs(msg, stderr);
fputc('\n', stderr);
exit(1);
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#define BUF_SIZE 256
#define NAME_SIZE 20
void *recv_msg(void *arg);
void *send_msg(void *arg);
void error_handling(char *msg);
char name[NAME_SIZE] = "[default]";
char msg[BUF_SIZE];
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in serv_addr;
pthread_t send_thread;
pthread_t recv_thread;
void *thread_return;
if (argc != 4)
{
printf("Usage : %s <IP> <port> <name>\n", argv[0]);
exit(1);
}
sprintf(name, "[%s]", argv[3]);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sock)
{
error_handling("fail to socket");
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(argv[2]));
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
if (-1 == connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)))
{
error_handling("fail to connect");
}
//创建发送消息线程
pthread_create(&send_thread, NULL, send_msg, (void *)&sock);
//创建接受消息线程
pthread_create(&recv_thread, NULL, recv_msg, (void *)&sock);
//pthread_join()的作用可以这样理解:主线程等待子线程的终止
pthread_join(send_thread, &thread_return);
pthread_join(recv_thread, &thread_return);
close(sock);
return 0;
}
void *send_msg(void *arg)
{
int sock = *((int *)arg);
char name_msg[NAME_SIZE + BUF_SIZE];
while (1)
{
fgets(msg, BUF_SIZE, stdin);
if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n"))
{
close(sock);
exit(0);
}
sprintf(name_msg, "%s %s", name, msg);
write(sock, name_msg, strlen(name_msg));
}
return NULL;
}
void *recv_msg(void *arg)
{
int sock = *((int *)arg);
char name_msg[NAME_SIZE + BUF_SIZE];
int str_len;
while (1)
{
str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE -1);
name_msg[str_len] = 0;
fputs(name_msg, stdout);
}
return NULL;
}
void error_handling(char *msg)
{
fputs(msg, stderr);
fputc('\n', stderr);
exit(1);
}
编译运行:
gcc chat_server.c -D_REENTRANT -o cserv -lpthread
gcc chat_clnt.c -D_REENTRANT -o cclnt -lpthread
./cserv 8180
./cclnt 127.0.0.1 8180 大郎
./cclnt 127.0.0.1 8180 小潘