网络io与tcp服务器
网络io与tcp服务器
一、网络IO与项目简介
1.网络IO
本文主要在linux上用c语言实现一个tcp server,在这个场景下可以把网络IO 理解为客户端与服务器之间通信的通道。适用场景比如:
- 各种即使通讯软件,例如微信
- 刷抖音短视频
- github/gitlab 上 git clone
2.tcp server 项目简介
本文主要通过实现一个tcp server来学习网络编程的知识,服务器基本结构如下:
- listen 监听套接字:监听是否有新的客户端请求,有则创建一个新的tcp连接与clientfd
- tcp连接:listen收到新的客户端请求后为该客户端创建一个tcp连接
- clientfd 客户端文件描述符:创建tcp连接后通过accept函数为该连接创建一个描述符,服务器通过该描述符来标识这个tcp连接以便于与客户端进行通信。
二、代码实现
1.服务器创建
//创建服务器监听套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
//服务器地址
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htons(INADDR_ANY);//0.0.0.0:本机任意一块网卡都可以
servaddr.sin_port = htons(8888);//0-1023为系统默认的,大于1023的可自定义
//将监听套接字与服务器地址绑定
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
printf("bind failed: %s
",strerror(errno));
}
//开始监听
listen(sockfd, 10);
首先创建一个监听套接字socket,sockfd即为这个套接字的描述符。然后设置服务器地址并将监听套接字与该地址绑定,最后进入监听状态,此时已经可以接收客户端请求,可查看8888号端口已经进入监听状态:
2.与客户端建立连接并通信
//建立与客户端的连接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
//接收数据
char buffer[1024];
int count = recv(clientfd, buffer, 1024, 0);
printf("%s
", buffer);
//发送信息
count = send(clientfd, buffer, count, 0);
getchar();
return 0;
此时与客户端已建立了tcp连接,客户端可以与服务器建立连接并收发信息。
- 建立连接
- 收发信息
但此时我们只能与一个客户端进行通信,虽然可以和另一个客户端建立tcp连接,但由于只有一个clientfd,所以无法与另一个客户端进行通信。
- 与两个客户端建立tcp连接
3.通过while循环实现与多个客户端的通信
为了解决上述问题,考虑添加一个while循环来实现与多个客户端的通信
//建立与客户端的连接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
while(1){
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf("client connect
");
//接收数据
char buffer[1024];
int count = recv(clientfd, buffer, 1024, 0);
printf("%s
", buffer);
//发送信息
count = send(clientfd, buffer, count, 0);
}
通过上述改进改代码可通过循环创建clientfd来实现与客户端的通信,但仍有一些问题。此时连接了两个客户端,可以看到显示只有一个client connect
这是因为在执行第一个循环时代码阻塞在了int count = recv(clientfd, buffer, 1024, 0);
这一行,由于第一个客户端没有发送信息线程就一直在此处等待接收第一个客户端的信息,无法进入下一个循环,上一个客户端发送信息之后才能与下一个客户端进行通信。
4.一请求一线程
为了解决上述线程阻塞的问题,考虑为每个客户端都建立一个线程专门为其服务
//建立与客户端的连接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
while(1){
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
pthread_t thid;
pthread_create(&thid, NULL, client_routine, &clientfd);
}
//回调函数
void *client_routine(void *arg){
int clientfd = *(int *)arg;
printf("client connect
");
//接收数据
char buffer[1024];
int count = recv(clientfd, buffer, 1024, 0);
printf("%s
", buffer);
//发送信息
count = send(clientfd, buffer, count, 0);
}
此时可做到与每个客户端的即时通讯
- 分别连接两个客户端
- 与两个客户端通信
三、技术总结
最后虽然通过一请求一线程的方式实现了即时通信,但一请求一线程的方案依旧有很多弊端:
- 资源消耗大
- 线程切换调度开销大
- 可扩展性受限,难以应对高并发
- 线程管理复杂,性能不稳定,线程间会相互影响
这些问题依旧有待改进。
文章参考<零声教育>的C/C++linux系统教程学习:https://github.com/0voice