1.基础介绍
网络io是指使用套接字(socket)进行网络连接后,实现输入输出的操作(I nput,Output),是网络通信的基础。
服务器是可以接收和发出信息的载体。
2.实现流程
2.1搭建基础服务器
创建套接字
cpp
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
//创建失败的处理
if(-1 == sockfd){
printf("sockfd: %s\n", strerror(errno));
exit(1);
}
初始化服务器配置
cpp
//存储地址信息的结构体
struct sockaddr_in servaddr;
//使用IPV4协议
servaddr.sin_family = AF_INET;
//允许本机任意IP地址访问
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//设定端口号,0-1023需要root权限
servaddr.sin_port = htons(8080);
将套接字与服务器信息绑定
cpp
//通过返回值判断绑定是否成功
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
printf("bind failed: %s\n", strerror(errno));
exit(1);
}
开启监听
cpp
listen(sockfd, 10);
//监听成功提示
printf("listen finished\n");
程序结束
cpp
//通过读取字符函数,暂停程序,防止过早结束
getchar();
//退出提示
printf("exit\n");
运行程序,输出如下(监听功能正常)
listen finished
使用 netstat -anop | grep 8080指令可以显示与指定端口相关的网络连接信息,如下
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 18353/./2.1.1io off (0.00/0/0)
成功创建一个不限制客户端ip的tcp服务器,且处于监听状态
使用网络助手建立对应ip和端口的连接后,再次检查端口相关信息,如下
tcp 1 0 0.0.0.0:8080 0.0.0.0:* LISTEN 18353/./2.1.1io off (0.00/0/0)
tcp 32 0 192.168.147.130:8080 192.168.147.1:56200 ESTABLISHED - off (0.00/0/0)
成功建立tcp连接,且发送了32字节的数据
2.2实现接收和返回数据功能
声明客户端地址变量
cpp
struct sockaddr_in clientaddr;
//计算长度用于接收数据函数的参数
socklen_t len = sizeof(clientaddr);
等待接收客户端连接请求
cpp
//提示开始接收
printf("accept\n");
//accept函数为监听套接字接收一个客户端的连接请求
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
//接收失败的处理
if(-1 == clientfd){
printf("accept: %s\n", strerror(errno));
exit(1);
}
//提示接收成功
printf("accept finished\n");
接收并打印数据
cpp
//存储接收数据的缓冲区变量
char buffer[1024] = {0};
//从客户端套接字中读取数据并存储到缓冲区中
int count = recv(clientfd, buffer, 1024, 0);
//打印数据
printf("RECV: %s\n", buffer);
再向客户端返回数据
cpp
//返回接收数据长度的数据,防止发送错误
count = send(clientfd, buffer, count, 0);
//打印发送长度
printf("send: %d\n", count);
运行程序
程序启动时输出
listen finished
客户端进行连接后输出
accept
accept finished
客户端发送数据后输出
RECV: http://www.baidu.com
send: 20
客户端页面也接受到同样数据,接收和返回数据功能正常运行
2.2完善功能
2.2.1接收多个连接请求
将接受连接请求到返回数据加入while循环中
cpp
while(1){
//提示开始接收
printf("accept\n");
...
//打印发送长度
printf("send: %d\n", count);
}
客户端发起两个连接
netstat -anop | grep 8080指令查看
tcp 0 0 0.0.0.0:8010 0.0.0.0:* LISTEN 18731/./2.1.1io off (0.00/0/0)
tcp 20 0 192.168.147.130:8080 192.168.147.1:58633 ESTABLISHED 18731/./2.1.1io off (0.00/0/0)
tcp 0 0 192.168.147.130:8080 192.168.147.1:58631 ESTABLISHED 18731/./2.1.1io off (0.00/0/0)
服务器输出结果,开启监听后,两次接收连接请求以及接收和返回数据,并等待下一次连接请求
listen finished
accept
accept finished
RECV: http://www.baidu.com
send: 20
accept
accept finished
RECV: http://www.baidu.com
send: 20
accept
2.2.2数据处理受客户端连接顺序影响
在建立连接后,若客户端没有按照连接顺序发送数据,则第一个客户端在while循环中的流程没有完全结束,导致后续客户端的数据服务器无法合理接收和返回
通过创建线程独立处理每个客户端请求
cpp
//存储线程编号的变量
pthread_t thid;
//创建线程,执行对数据的接收和返回操作
pthread_create(&thid, NULL, client_thread, &clientfd);
将对客户端数据的处理封装到函数中
cpp
void *client_thread(void *arg){
//接收客户端套接字
int clientfd = *(int *)arg;
//循环接收多次数据
while(1)
{
char buffer[1024] = {0};
int count = recv(clientfd, buffer, 1024, 0);
printf("RECV: %s\n", buffer);
count = send(clientfd, buffer, count, 0);
printf("send: %d\n", count);
}
}
运行程序,输出结果
listen finished
accept
accept finished
accept
accept finished
accept
RECV: http://www.baidu.com
send: 20
RECV: http://www.baidu.com
send: 20
RECV: http://www.baidu.com
send: 20
客户端数据处理互不影响,且可以多次发送
3.总结
3.1概述
使用io进行服务器与客户端的数据交互操作,流程可概括为:创建套接字和服务器,用 bind 绑定,listen 开启监听,循环 accept 接收客户端连接请求,通过 pthread_create 创建线程独立处理多个连接,数据处理 recv 接收数据,send 返回数据。
3.2优化方向
每个连接都独立创建线程对资源消耗大,且线程数量受硬件性能限制。
完整代码
cpp
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<pthread.h>
#include<unistd.h>
void *client_thread(void *arg){
//接收客户端套接字
int clientfd = *(int *)arg;
//循环接收多次数据
while(1)
{
//存储接收数据的缓冲区变量
char buffer[1024] = {0};
//从客户端套接字中读取数据并存储到缓冲区中
int count = recv(clientfd, buffer, 1024, 0);
//打印数据
printf("RECV: %s\n", buffer);
//返回接收数据长度的数据,防止发送错误
count = send(clientfd, buffer, count, 0);
//打印发送长度
printf("send: %d\n", count);
}
}
int main(){
//创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
//创建失败的处理
if(-1 == sockfd){
printf("sockfd: %s\n", strerror(errno));
exit(1);
}
//初始化服务器信息
struct sockaddr_in servaddr;
//使用IPV4协议
servaddr.sin_family = AF_INET;
//允许本机任意IP地址访问
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//设定端口号,0-1023需要root权限
servaddr.sin_port = htons(8080);
//通过返回值判断绑定是否成功
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
printf("bind failed: %s\n", strerror(errno));
exit(1);
}
//开启监听
listen(sockfd, 10);
//监听成功提示
printf("listen finished\n");
//声明客户端地址变量
struct sockaddr_in clientaddr;
//计算长度用于接收数据函数的参数
socklen_t len = sizeof(clientaddr);
while(1){
//提示开始接收
printf("accept\n");
//accept函数为监听套接字接收一个客户端的连接请求
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
//接收失败的处理
if(-1 == clientfd){
printf("accept: %s\n", strerror(errno));
exit(1);
}
//提示接收成功
printf("accept finished\n");
//存储线程编号的变量
pthread_t thid;
//创建线程,执行对数据的接收和返回操作
pthread_create(&thid, NULL, client_thread, &clientfd);
}
//通过读取字符函数,暂停程序,防止过早结束
getchar();
//退出提示
printf("exit\n");
return 0;
}