- 多线程实现TCP并发服务器
- 前言
- 原理
- 代码实现
- 服务器---01server.c
- 客户端---02client.c
- 执行结果
- 6.非原创
服务器模型分为两种,循环服务器、并发服务器。
循环服务器: 服务器在同一时刻只能处理同一个客户端的请求。
并发服务器: 服务器在同一时刻能处理多个客户端的请求。
原理TCP服务器默认的就是一个循环服务器,因为内部有两个阻塞的函数, accept recv 会相互影响
UDP服务器默认的就是一个并发服务器,因为只有一个阻塞的函数 recvfrom
代码实现 服务器—01server.c主线程负责 accept 等待客户端连接,一旦有新的客户端连接到服务器,就立即创建一个子线程,在子线程的线程处理函数中专门处理该客户端的读写请求。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERRLOG(errmsg) \
do \
{ \
printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
perror(errmsg); \
exit(-1); \
} while (0)
//需要将该客户端的 acceptfd 和 网络信息结构体传给子线程
typedef struct
{
int accept_fd;
struct sockaddr_in client_addr;
} num_t;
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[]);
//子线程处理
void *task1(void *arg);
int main(int argc, const char *argv[])
{
//检测命令行参数个数
if (3 != argc)
{
printf("Usage : %s \n" , argv[0]);
exit(-1);
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int sockfd=socket_bind_listen(argv);
//用来保存客户端信息的结构体
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
//传递信息的结构体
num_t ku;
memset(&ku, 0, sizeof(ku));
//创建线程
pthread_t tid;
while (1)
{
//阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
ku.accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
if (-1 == ku.accept_fd)
ERRLOG("accept error");
//inet_ntoa 将32位网络字节序二进制地址转换成点分十进制的字符串
//ntohs将无符号2字节整型 网络-->主机
printf("客户端 (%s:%d) 连接了\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
//创建子线程处理新客户端的读写请求
//需要将该客户端的 acceptfd 和 网络信息结构体传给子线程
ku.client_addr = client_addr;
if (pthread_create(&tid, NULL, task1, (void *)&ku))
ERRLOG("create tid2 error");
//设置线程分离属性,无需手动回收资源
//分离态的线程结束后资源会被自动回收,这个函数不会阻塞
pthread_detach(tid);
}
//关闭监听套接字 一般不关闭
close(sockfd);
return 0;
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[])
{
// 1.创建套接字 //IPV4 //TCP
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
ERRLOG("socket error");
// 2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));//清空
server_addr.sin_family = AF_INET;//IPV4
//端口号 填 8888 9999 6789 ...都可以
//atoi字符串转换成整型数
//htons将无符号2字节整型 主机-->网络
server_addr.sin_port = htons(atoi(argv[2]));
// ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
//inet_addr字符串转换成32位的网络字节序二进制值
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
// 3.将套接字和网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("bind error");
//将套接字设置成被动监听状态
if (-1 == listen(sockfd, 10))
ERRLOG("listen error");
return sockfd;
}
//子线程处理
void *task1(void *arg)
{
//线程的处理函数
num_t ku = *(num_t *)arg;
char buff[128] = {0};//临时存放数据
int ret = 0;
//接收客户端发来的数据
while (1)
{
if (0 > (ret= recv(ku.accept_fd, buff,sizeof(buff), 0)))
ERRLOG("recv error");
else if (0 == ret)//客户端侧CTRL+C
{
printf("客户端 (%s:%d) 断开连接\n",
inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port));
break;
}
else
{
if (0 == strcmp(buff, "quit"))
{
printf("客户端 (%s:%d) 退出了\n",
inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port));
break;
}
printf("客户端 (%s:%d) 发来数据:[%s]\n",
inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port), buff);
//组装回复给客户端的应答
strcat(buff, "---996");
//回复应答
if (0 > (ret = send(ku.accept_fd, buff,sizeof(buff), 0)))
ERRLOG("send error");
}
}
//关闭该客户端的套接字
close(ku.accept_fd);
//线程退出
pthread_exit(NULL);
}
客户端—02client.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERRLOG(errmsg) do{\
printf("%s--%s(%d):", __FILE__, __func__, __LINE__);\
perror(errmsg);\
exit(-1);\
}while(0)
int main(int argc, const char *argv[])
{
//检测命令行参数个数
if(3 != argc){
printf("Usage : %s \n" , argv[0]);
exit(-1);
}
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
ERRLOG("socket error");
//2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
//端口号 填 8888 9999 6789 ...都可以
server_addr.sin_port = htons(atoi(argv[2]));
//ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
//与服务器建立连接
if(-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("connect error");
printf("---连接服务器成功---\n");
char buff[128] = {0};
while(1){
scanf("%s", buff);
int ret = 0;
if(0 > (ret = send(sockfd, buff,sizeof(buff), 0)))
ERRLOG("send error");
if(0 == strcmp(buff, "quit"))
break;
if(0 > (ret = recv(sockfd, buff,sizeof(buff), 0)))
ERRLOG("recv error");
printf("收到服务器回复:[%s]\n", buff);
}
//关闭套接字
close(sockfd);
return 0;
}
执行结果
6.非原创
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)