一、多线程实现
每当有一个客户端连接上,就创建一个专门线程与这个客户端通信,在线程里面不停的接受这个客户端发来的消息,如果有一个100客户端就会产生100个线程去接受每一个客户端的消息,从而实现了多人在线的效果服务器
二、创建一个简易的服务器
上篇文章有教程【Linux C | 网络编程】| IPv4地址族、IPv4地址族和字符地址间的转换、简易的TCP连接-CSDN博客
三、服务器多线程
服务器每被客户机连接一次,就会创建一个对应子线程。
子线程负责实现对客户机的读写功能。
同时子线程要判断对应客户机的状态,如果该客户机退出, read 函数返回 0 ,将状态值val 设为1,修改转发信息,再退出线程。
//创建一个简易的多人聊天室
#include <myhead.h>
//定义结构体来接收客户端的网络地址
struct sockaddr_in CliAddr;
int len = sizeof(CliAddr);
//定义一个数组来接收客户端socket号,用来创建多线程
int clientIdArr[999];
int clientIndex = 0; //用来记录数组的下标
int val; //定义一个状态值
//线程作用函数
void *func(void *p)
{
int fd = *(int *)p;
while (1)
{
char buf[512];
if (read(fd, buf, sizeof(buf)) <= 0) //判断文件(客户端)是否存在
{
val = 1;
}
//转发信息
for (int i = 0; i < clientIndex; i++)
{
if (fd != clientIdArr[i])
{
if (val == 1)
{
//转发内容,如果没有客户机退出则不执行
char arr[128];
inet_ntop(AF_INET, &CliAddr.sin_addr.s_addr, arr, sizeof(arr));
sprintf(buf, "网络地址为%s:%d的客户机退出了群聊", arr, ntohs(CliAddr.sin_port));
}
write(clientIdArr[i], buf, sizeof(buf));
}
}
//客户机退出,服务器则结束该线程
if (val == 1)
{
break;
}
}
return NULL;
}
int main(int argc, const char *argv[])
{
//获取一个网络地址
u_int32_t ip;
inet_pton(AF_INET, "192.168.40.148", &ip);
struct sockaddr_in addr;
addr.sin_addr.s_addr = ip;
addr.sin_port = htons(8881);
addr.sin_family = AF_INET;
// 1、获取一个socket
int ListenSd = socket(AF_INET, SOCK_STREAM, 0);
if (ListenSd < 0)
{
perror("");
return -1;
}
// 2、绑定网络地址
bind(ListenSd, (struct sockaddr *)&addr, sizeof(addr));
// 3、监听连接
listen(ListenSd, 5);
while (1)
{
// 4、等待连接
int clientID = accept(ListenSd, (struct sockaddr *)&CliAddr, &len);
if (clientID < 0)
{
perror("accept error");
return -1;
}
//将接收到的套接字存入数组
clientIdArr[clientIndex] = clientID;
//把数字网络地址转为字符串地址
char arr[128];
inet_ntop(AF_INET, &CliAddr.sin_addr.s_addr, arr, sizeof(arr));
printf("ip地址为%s的%d端口连接上了该服务器\n", arr, ntohs(CliAddr.sin_port));
//创建子线程,传入参数是连接到的套接字ID
pthread_t ptid;
pthread_create(&ptid, NULL, func, &clientIdArr[clientIndex]);
clientIndex++;
}
//绑定
return 0;
}
四、客户机多线程
客户机的主线程对服务器发送信息,子线程接收服务器信息,同时判断服务器的信息,如果服务器异常退出,则断开连接退出程序。
#include <myhead.h>
//线程工作函数
void *func(void *p)
{
int fd = *(int *)p;
while (1)
{
char buf[512];
//判断服务器的状态
if(read(fd, buf, sizeof(buf)) <= 0)
{
printf("服务器异常,程序退出\n");
exit(0);
}
printf("收到了%s\n", buf);
}
}
int main(int argc, const char *argv[])
{
//获取一个网络地址
u_int32_t ip;
inet_pton(AF_INET, "192.168.40.148", &ip);
struct sockaddr_in addr;
addr.sin_addr.s_addr = ip;
addr.sin_port = htons(8881);
addr.sin_family = AF_INET;
// 1、获取一个socket
int sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0)
{
perror("");
return -1;
}
//连接主机
if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("connect error");
return -1;
}
//定义一个线程来接收信息
pthread_t ptid;
pthread_create(&ptid, NULL, func, &sd);
//发送信息
while (1)
{
char buf[512];
scanf("%s", buf);
write(sd, buf, sizeof(buf));
}
return 0;
}
五、一些转换函数的补充
在编写该代码的时候,老是混淆 ip的 数字和字符串的转换函数。
还有端口号网络字节序和主机字节序的转换
inet_pton :
用于把字符串IP转换为数字ip;
u_int32_t ip;
inet_pton(AF_INET, "192.168.40.148", &ip);
inet_ntop:
把数字ip转换为字符串IP;
char arr[128];
inet_ntop(AF_INET, &CliAddr.sin_addr.s_addr, arr, sizeof(arr));
htons :16位
htonl:32位
将主机字节序转换为网络字节序
ntohs:16位
ntohl:32位
将网络字节序转位主机字节序
六、总结
只要理清思路和熟悉函数,实现该程序还是不难