最近从windows转到linux学习了一段时间了。其实tcp服务器本质基本都一样。
这次上传一个简单的linux下多线程并发服务器。该服务器可以接收128个客户端同时连接,并且加入了互斥锁,排除了多个客户端同时间连接时出现的问题。并且当前一个客户端关闭连接时,后面连接的客户端可以继续使用该线程。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define maxthread 256
typedef struct SockInfo
{
int sockfd;
struct sockaddr_in addr;
pthread_t id;
}SockInfo; //定义结构体,来存储多客户端的线程编号
pthread_mutex_t mutex; // 定义互斥锁,全局变量
/************************************************************************
函数名称: void *client_process(void *arg)
函数功能: 线程函数,处理客户信息
函数参数: 已连接套接字
函数返回: 无
************************************************************************/
void *client_process(void *arg)
{
int recv_len = 0;
char recv_buf[1024] = ""; // 接收缓冲区
int connfd = *(int *)arg; // 传过来的已连接套接字
// 解锁,pthread_mutex_lock()唤醒,不阻塞
pthread_mutex_unlock(&mutex);
// 接收数据
while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
{
//printf("recv_buf: %s\n", recv_buf); // 打印数据
printf(" 来自fd描述符:%d,内容为: %s\n",connfd, recv_buf); // 打印数据
send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
memset(&recv_buf, 0, recv_len);
}
printf("fd:%d 关闭!\n",connfd);
close(connfd); //关闭已连接套接字
return NULL;
}
//===============================================================
// 语法格式: void main(void)
// 实现功能: 主函数,建立一个TCP并发服务器
// 入口参数: 无
// 出口参数: 无
//===============================================================
int main(int argc, char *argv[])
{
int sockfd = 0; // 套接字
int connfd = 0;
int err_log = 0;
struct sockaddr_in my_addr; // 服务器地址结构体
unsigned short port =8888 ; // 监听端口
pthread_t thread_id;
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的
printf("TCP Server Started at port %d!\n", port);
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
if(sockfd < 0)
{
perror("socket error");
exit(-1);
}
bzero(&my_addr, sizeof(my_addr)); // 初始化服务器地址
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
printf("Binding server to port %d\n", port);
int flag=1;//设置端口复用,这样如果是服务器主动断开连接后可以马上使用,略过time_wait阶段的等待
setsockopt(sockfd,SOL_SOCKET,SO_REUSEPORT,&flag,sizeof(flag));
// 绑定
err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
if(err_log != 0)
{
perror("bind");
close(sockfd);
exit(-1);
}
// 监听,套接字变被动
err_log = listen(sockfd,4);
if( err_log != 0)
{
perror("listen");
close(sockfd);
exit(-1);
}
printf("Waiting client...\n");
int i=0;//线程的标志
SockInfo info[maxthread];
for(int i=0;i<maxthread;++i)
{
info[i].sockfd=-1;
}
socklen_t cli_len=sizeof(struct sockaddr_in);
while(1)
{
char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客户端IP地址
for(i=0;i<maxthread;++i)
{
if(info[i].sockfd==-1)//遍历线程,找到最小的空闲描述符,i为其结构体标志
{
break;
}
}
if(i==maxthread)break;//如果线程已经满了,不接受连接退出循环
// 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞
pthread_mutex_lock(&mutex);
//获得一个已经建立的连接
info[i].sockfd = accept(sockfd, (struct sockaddr*)&info[i].addr, &cli_len);
// 打印客户端的 ip 和端口
inet_ntop(AF_INET, &info[i].addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("--------client ip=%s,port=%d\n", cli_ip,ntohs(info[i].addr.sin_port));
if(info[i].sockfd > 0)
{
//给回调函数传的参数,&connfd,地址传递
pthread_create(&info[i].id, NULL, client_process, (void *)&info[i]); //创建线程
pthread_detach(info[i].id); // 线程分离,结束时自动回收资源
}
}
close(sockfd);
return 0;
}