目录
三、select、setsockopt/getsockopt函数说明
一、服务器模型
服务器模型分为两种:循环服务器、并发服务器
循环服务器:在同一个时刻只能响应一个客户端的请求。
并发服务器:在同一时刻可以响应多个客户端的请求。
TCP服务器默认就是一个循环服务器,受两类阻塞函数accept和recv之间的影响。
UDP服务器默认就是一个并发服务器,因为只有一个会造成阻塞的函数(recvfrom)。
二、TCP并发服务器
在有些场景下我们即需要保证数据可靠,又需要支持并发,这样就需要用到TCP并发服务器了。
2.1使用多线程实现TCP并发服务器
思路:主线程负责accept等待客户端连接,一旦有客户端连接就创建一个子线程专门用于和当前的客户端通信。
服务器代码:
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define PRINT_ERR(msg) \
do { \
printf("%s %s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
return -1; \
} while (0)
typedef struct MSG {
char txt[128];
int acceptfd;
struct sockaddr_in clientaddr;
} msg_t;
// 线程体
void* deal_read_write(void* arg)
{
pthread_detach(pthread_self()); // 将线程设置成被动分离状态
msg_t buff = *(msg_t*)arg;
// 定义结构体保存客户端信息
int nbytes = 0; // 记录recv函数的返回值
int acceptfd;
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
acceptfd = buff.acceptfd;
clientaddr = buff.clientaddr;
while (1) {
// 收发数据
memset(&buff, 0, sizeof(buff));
if (-1 == (nbytes = recv(acceptfd, &buff, sizeof(buff), 0))) {
perror("recv error");
printf("%s %s:%d\n", __FILE__, __func__, __LINE__);
pthread_exit(EXIT_FAILURE);
} else if (0 == nbytes) {
printf("客户端[%s:%d]断开了连接..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
break;
}
if (!strcmp(buff.txt, "quit")) {
printf("客户端[%s:%d]退出了..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
break;
}
printf("[%s:%d]:[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff.txt);
// 组装应答回复客户端
strcat(buff.txt, "--server");
if (-1 == send(acceptfd, &buff, sizeof(buff), 0)) {
perror("send error");
printf("%s %s:%d\n", __FILE__, __func__, __LINE__);
pthread_exit(EXIT_FAILURE);
}
}
close(acceptfd);
pthread_exit(EXIT_SUCCESS);
}
int main(int argc, const char* argv[])
{
// 入参合理性检查
if (3 != argc) {
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
PRINT_ERR("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// 设置端口复用
int flag = 1;
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) {
printf("setsockopt error");
}
// 3.将套接字与服务器的网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("bind error");
}
// 4.将套接字设置成被动监听状态
if (-1 == listen(sockfd, 5)) {
PRINT_ERR("listen error");
}
// 定义结构体保存客户端信息
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
int nbytes = 0;
msg_t buff;
// 5.阻塞等待客户端连接
pthread_t tid;
int acceptfd = 0;
int fd = 0;
char file[128] = { 0 };
while (1) {
// 主线程收发数据
printf("正在等待客户端连接..\n");
if (-1 == (acceptfd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len))) {
PRINT_ERR("accept error");
}
printf("客户端[%s:%d]连接到服务器..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
buff.acceptfd = acceptfd;
buff.clientaddr = clientaddr;
// 创建子线程
if ((errno = pthread_create(&tid, NULL, deal_read_write, (void*)&buff)) != 0)
PRINT_ERR("pthread create error");
}
// 关闭套接字
close(sockfd);
return 0;
}
客户端代码:
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <net

最低0.47元/天 解锁文章
1162

被折叠的 条评论
为什么被折叠?



