网络编程学习——3
一、IO多路复用
1.poll函数
(1)poll
需要轮询,需要两次拷贝,监听的文件描述符不受限制
使用结构体数组存储文件描述符
功能
poll
函数是一种 I/O 多路复用机制(在select基础上优化),用于检测多个文件描述符的状态,判断它们是否有数据可读、可写或者发生异常等事件,从而实现同时处理多个 I/O 操作
函数头文件
#include <poll.h>
函数原型
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数
参数名 类型 作用与细节 fds
struct pollfd *
指向一个包含 struct pollfd
结构体的数组指针。每个struct pollfd
结构体代表一个要检测的文件描述符及其相关信息。nfds
nfds_t
指定 fds
数组中元素的数量,即要检测的文件描述符的个数。timeout
int
设置 poll
函数的超时时间,单位是毫秒(ms)。timeout > 0
:最多等待timeout
毫秒timeout = 0
:不等待,立即返回
timeout = -1
:一直阻塞,直到有文件描述符就绪才返回。
常见事件宏定义
宏定义 含义 POLLIN
表示有数据可读 POLLOUT
表示可写 POLLPRI
表示有紧急数据可读。 POLLERR
表示发生错误。 POLLHUP
表示被挂起。 POLLNVAL
表示 fd
不是一个有效的文件描述符
返回值
返回情况 含义说明 大于 0 成功,返回值是 fds
数组中就绪的文件描述符的数量等于 0 表示 poll
函数超时等于 - 1 表示 poll
函数调用失败,此时errno
会被设置
EBADF
:fds
数组中存在无效的文件描述符EFAULT
:fds
指针指向的内存区域无效EINTR
:函数被信号中断
结构体
struct pollfd { int fd; /* 文件描述符 */ short events; /* 等待的事件,如POLLIN(可读)、POLLOUT(可写)等 */ short revents; /* 实际发生的事件,由poll函数填充 */ };
(2)poll示例
/*===============================================
* 文件名称:poll.c
* 创 建 者:青木莲华
* 创建日期:2025年08月15日
* 描 述:并发TCP通信server 基于poll
================================================*/
#include "socket_head.h"
#define MAX 64
int main(int argc, char *argv[])
{
char buf[1024] = {0}; //缓冲区
int ret = -1; //recv返回值
struct sockaddr_in addr; //IPV4结构体
struct pollfd pfds[MAX]; //poll数组
char err_msg[24]; //错误信息
int nfds = 0; //文件描述符个数
socklen_t addlen = sizeof(addr); //客户端地址信息实际大小
//1.创建套接字 IPV4 TCP
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
strcpy(err_msg,"socket");
goto err;
}
//2.绑定套接字+初始化结构体
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);
addr.sin_addr.s_addr = inet_addr("192.168.18.220");
if(-1 == bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)))
{
strcpy(err_msg,"bind");
goto err;
}
//3.监听
if(-1 == listen(sockfd,5))
{
strcpy(err_msg,"listen");
goto err;
}
printf("Server is waiting for connection now!\n");
//4.初始化结构体数组
pfds[0].fd = sockfd;
pfds[0].events = POLLIN; //当前套接字可以读
nfds++;
while(1)
{
//5.poll
if(-1 == poll(pfds,nfds,-1))
{
strcpy(err_msg,"poll");
goto err;
}
//6.1遍历结构体数组 做相应操作
int i;
for(i = 0 ; i < nfds ; i++)
{
//6.2 是否是sockfd就绪(有新连接)
if(pfds[i].fd == sockfd && pfds[i].revents == POLLIN)
{
//6.2.1建立新连接
int connfd = accept(sockfd,(struct sockaddr *)&addr,&addlen);
if(-1 == connfd)
{
strcpy(err_msg,"accept");
goto err;
}
printf("Client [%s:%d] has connected!\n",
inet_ntoa(addr.sin_addr),
ntohs(addr.sin_port));
//6.2.2将新连接文件描述符加入结构体数组供poll处理
//找插入位置
int flag = -1;
for(int j = 1 ; j < nfds ; j++)
{
if(pfds[j].fd == -1)
{
flag = j;
}
}
if(-1 != flag)
{
pfds[flag].fd = connfd;
pfds[flag].events = POLLIN;
}
else
{
pfds[nfds].fd = connfd;
pfds[nfds].events = POLLIN;
nfds++;
}
}
else if(pfds[i].fd != -1 && pfds[i].revents == POLLIN)
{
//6.3有已连接的connfd就绪
//6.3.1读操作
ret = recv(pfds[i].fd,buf,sizeof(buf),0);
if(-1 == ret)
{
strcpy(err_msg,"recv");
goto err;
}
//6.3.2若Client 断开连接
if(0 == ret)
{
printf("A Client has disconnected!\n");
close(pfds[i].fd);
pfds[i].fd = -1;
continue;
}
puts(buf);
memset(buf,0,sizeof(buf));
}
}
}
close(sockfd);
return 0;
//错误处理
err:
perror(err_msg);
return -1;
}
2.epoll机制
不需要轮询,只需要一次拷贝,文件描述符不受限
核心机制:红黑树+双向链表 实现
核心使用流程
- 创建epoll
- 插入关心文件描述符到epoll
- 让内核监听
- 处理就绪的文件描述符
- 添加新的文件描述符/删除退出的文件描述符
(1)创建epoll(epoll_create
函数)
功能
Linux 系统特有的 I/O 多路复用机制
epoll
的初始化函数,用于创建一个epoll
实例(事件表),用于后续监听多个文件描述符的 I/O 事件
函数头文件
#include <sys/epoll.h>
函数原型
int epoll_create(int size);
参数
仅需传入一个大于 0 的值即可,早期 Linux 版本中,size 表示 epoll 实例可监听的最大文件描述符数量(需大于 0),用于内核预先分配资源。现代 Linux 系统(2.6.8 及以上)中,size 参数已被忽略,内核会动态调整资源,不再受限于该值。
返回值
成功——返回epoll文件描述符(epfd)
失败——返回 -1 ,并设置errno
(2) 操作 epoll 实例(epoll_ctl
函数)
功能
用于操作
epoll
实例(事件表),实现 添加、修改、删除 文件描述符的监听事件,是epoll
机制中管理文件描述符与事件关联的核心函数。
函数头文件
#include <sys/epoll.h>
函数原型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数
参数名 类型 作用与细节 epfd
int
epoll
实例的文件描述符(由epoll_create
返回,标识要操作的事件表)op
int
EPOLL_CTL_ADD
:添加fd
到epoll
实例,监听event
事件EPOLL_CTL_DEL
:删除fd
及关联事件EPOLL_CTL_MOD
:修改fd
的监听事件为event
fd
int
要操作的文件描述符(如套接字 socket
、管道pipe
等)event
struct epoll_event*
指向 epoll_event
结构体的指针,描述监听的事件类型(如可读、可写),仅EPOLL_CTL_DEL
时可传NULL
返回值
成功——返回0
失败——返回-1,并设置errno
struct epoll_event
结构体
epoll_ctl
通过该结构体定义监听的事件,原型如下:struct epoll_event { uint32_t events; // 事件类型(位掩码),如 EPOLLIN(文件描述符可读)、EPOLLOUT(可写) epoll_data_t data;// 用户数据(可存 fd、指针等,用于事件回调时区分来源) }; // 其中 epoll_data_t 是联合类型: typedef union epoll_data { void *ptr; // 指针(可自定义数据) int fd; // 文件描述符(常用,直接关联监听的 fd) uint32_t u32; // 无符号整数 uint64_t u64; // 无符号长整数 } epoll_data_t;
(3)让内核监听(epoll_wait
函数)
功能
用于等待在
epoll
实例中注册的文件描述符上有事件发生。当有事件就绪(如可读、可写、异常等),函数会返回就绪事件的数量,并将就绪事件信息填充到指定的数组中,方便开发者进行后续处理。
函数头文件
#include <sys/epoll.h>
函数原型
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数
参数名 类型 作用与细节 epfd
int
epoll
实例的文件描述符events
struct epoll_event *
指向 epoll_event
结构体数组的指针,用于存储epoll
检测到的就绪事件。函数返回时,该数组会被填充。maxevents
int
表示 events
数组的大小,即最多能返回的就绪事件数量。timeout
int
timeout > 0
:最多等待timeout
毫秒后返回。timeout = 0
:不等待,立即返回,检查是否有就绪事件。timeout = -1
:一直阻塞,直到有事件发生才返回。
struct epoll_event
结构体(补充说明)
epoll_wait
会将就绪事件的信息填充到events
数组中的epoll_event
结构体里,其原型如下:struct epoll_event { uint32_t events; // 事件类型(位掩码),如 EPOLLIN(文件描述符可读)、EPOLLOUT(可写) epoll_data_t data;// 用户数据(可存 fd、指针等,用于事件回调时区分来源) }; // 其中 epoll_data_t 是联合类型: typedef union epoll_data { void *ptr; // 指针(可自定义数据) int fd; // 文件描述符(常用,直接关联监听的 fd) uint32_t u32; // 无符号整数 uint64_t u64; // 无符号长整数 } epoll_data_t;
返回值
返回情况 含义说明 大于 0 成功,返回值是 events
数组中就绪事件的数量
,可通过遍历events
数组获取具体的就绪事件信息并处理。等于 0 表示 epoll_wait
超时,在指定的timeout
时间内没有检测到任何就绪事件。等于 -1 失败,此时 errno
会被设置,
常见错误原因包括:EBADF
:epfd
不是有效的文件描述符。
EINTR
:函数被信号中断。
(4)处理准备好的文件描述符
示例
//1.先要获取epoll_wait函数返回的就绪epoll_event的个数
int size = epoll_wait(......);
//此处省略错误处理
//2.遍历(假设结构体数组为event_arr)
for(int i = 0 ; i < size ; i++)
{
//如果读就绪(EPOLLIN)
if(event_arr[i].events & EPOLLIN)
{
int fd = -1;
//获取文件描述符然后进行操作
fd = event_arr[i].data.fd;
//假设要实现recv读操作
int ret = recv(fd,buf,sizeof(buf));
//此处省略后续操作
}
}
(5)添加/删除(EPOLL_CTL_ADD / EPOLL_CTL_DEL
)
int fd = 0; // 示例文件描述符(标准输入)
struct epoll_event event;
// 添加文件描述符(EPOLL_CTL_ADD)
event.events = EPOLLIN; // 关注读事件
event.data.fd = fd;
if (-1 == epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event)) {
perror("EPOLL_CTL_ADD failed");
return 1;
}
printf("已添加fd: %d\n", fd);
// 业务逻辑处理...
// 删除文件描述符(EPOLL_CTL_DEL)
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
perror("EPOLL_CTL_DEL failed");
return 1;
}
printf("已删除fd: %d\n", fd);
close(epoll_fd);
(6)epoll示例
/*===============================================
* 文件名称:epoll.c
* 创 建 者:青木莲华
* 创建日期:2025年08月16日
* 描 述:Server(TCP通信+epoll并发)
================================================*/
#include "socket_head.h"
#define MAX 64
int main(int argc, char *argv[])
{
char buf[1024] = {0}; //缓冲区
char err_msg[24] = {0}; //错误信息
int ret = -1; //recv返回值
int sockfd; //服务器套接字
int epfd; //epoll文件描述符
struct sockaddr_in addr; //IPV4 结构体
socklen_t addrlen = sizeof(addr); //客户端地址长度
struct epoll_event ep; //epoll结构体
struct epoll_event eparr[MAX]; //epoll结构体数组
//1.创建套接字
sockfd = socket(AF_INET,SOCK_STREAM,0); //服务器套接字(IPV4 + TCP)
if(-1 == sockfd)
{
strcpy(err_msg,"socket");
goto err;
}
//2.1 初始化地址结构体
addr.sin_family = AF_INET; //协议族为IPV4
addr.sin_port = htons(6666); //端口号为6666
addr.sin_addr.s_addr = inet_addr("192.168.18.220"); //设置服务器IP
//2.2 绑定套接字
if(-1 == bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)))
{
strcpy(err_msg,"bind");
goto err;
}
//3.监听
if(-1 == listen(sockfd,5))
{
strcpy(err_msg,"listen");
goto err;
}
printf("Server is waiting for connecting now!\n");
//4.1 创建epoll
epfd = epoll_create(1);
if(-1 == epfd)
{
strcpy(err_msg,"epoll_create");
goto err;
}
//4.2 将服务器套接字在epoll内初始化
ep.events = EPOLLIN;
ep.data.fd = sockfd;
if(-1 == epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ep)) //添加进event数组
{
strcpy(err_msg,"epoll_ctl");
goto err;
}
//5.通信
while(1)
{
//5.1 epoll_wait 阻塞等待就绪
int size = epoll_wait(epfd,eparr,MAX,-1);
if(-1 == size)
{
strcpy(err_msg,"epoll_wait");
goto err;
}
//5.2 遍历events数组 对就绪的连接进行操作
int i;
for(i = 0 ; i < size ; i++)
{
//5.2.1 就绪状态为可读
if(eparr[i].events & EPOLLIN)
{
//5.2.2 是否是sockfd,是就是有新连接
if(eparr[i].data.fd == sockfd)
{
int connfd = accept(sockfd,(struct sockaddr *)&addr,&addrlen);
if(-1 == connfd)
{
strcpy(err_msg,"accept");
goto err;
}
printf("Client [%s:%d] has connected!\n",
inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
ep.data.fd = connfd;
ep.events = EPOLLIN;
//ADD新连接
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ep);
}
else
{
//接收客户端
ret = recv(eparr[i].data.fd,buf,sizeof(buf),0);
if(-1 == ret)
{
strcpy(err_msg,"recv");
close(eparr[i].data.fd);
continue;
}
else if(0 == ret)
{
//客户端断开连接
close(eparr[i].data.fd);
printf("A Client has disconnected!\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,eparr[i].data.fd,NULL);
}
else
{
puts(buf);
memset(buf,0,sizeof(buf));
}
}
}
}
}
close(sockfd);
return 0;
//错误处理
err :
perror(err_msg);
return -1;
}
二、客户端和服务器通信(练习)
功能:登录+注册,用户数据保存在服务器路径下的login_data.txt
server.c
/*===============================================
* 文件名称:server.c
* 创 建 者:青木莲华
* 创建日期:2025年08月16日
* 描 述:简单登录/通信 Server(基于IPV4+TCP+epoll)
================================================*/
#include "socket_head.h"
//登录数据文件路径
#define PATH "./login_data.txt"
//最大并发数
#define MAXFDS 64
//端口号
#define PORT 31111
//ip地址
#define IP "192.168.6.174"
//账号信息结构体
typedef struct
{
char username[20];
char password[20];
}data_t;
//所有用户信息
data_t users[100];
//通信结构体
typedef struct
{
//head
//数据data的长度
int length;
//客户端想做的操作 :1.登录 2.正常发送数据
short op;
//body
char data[128];
}msg_t;
//通信缓冲区
msg_t msg;
int server_fd = -1; //服务器套接字 文件描述符
int epfd = -1; //epoll 文件描述符
FILE *fp = NULL; //login_data.txt 文件指针
int count = 0; //用户信息个数
int ready = 0; //关注文件描述符 就绪数
struct sockaddr_in addr; //地址结构体
socklen_t len; //客户端地址大小
struct epoll_event ev; //epoll 结构体 用于EPOLL_CTL_ADD
struct epoll_event evs[MAXFDS]; //epoll 结构体数组 用于接收 epoll_wait
char buf[128] = {0}; //文件缓冲区
//全局错误处理
void error_exit(char *err_msg)
{
if(-1 < server_fd)
close(server_fd);
if( NULL != fp)
fclose(fp);
if(-1 < epfd)
close(epfd);
perror(err_msg);
exit(0);
}
/*
函数功能:查找账号密码是否正确
函数参数:name 账号 psw 密码
返回值 :成功返回 用户信息中的index
失败返回 -1(账号或这密码错误)
*/
int login(char *name,char *psw)
{
int i;
for(i = 0 ; i < count ; i++)
{
//用户名和密码是否匹配
if(0 == strcmp(users[i].username,name) && 0 == strcmp(users[i].password,psw))
return i;
}
return -1;
}
/*
函数功能:注册(注册信息保存到login_data.txt)
函数参数:name 账号 psw 密码
返回值 :成功返回 1
账号存在 0
失败返回 -1
*/
int regist(char *name,char *psw)
{
if(count+1 >= 100)
return -1;
//判断账号是否重复
int i;
for(i = 0 ; i < count ; i++)
{
if(0 == strcmp(users[i].username,name))
return 0;
}
fp = fopen(PATH,"a");
if(NULL == fp)
return -1;
memset(buf,0,sizeof(buf));
//写入文件内
sprintf(buf,"%s : %s\n",name,psw);
if(1 > fwrite(buf,strlen(buf),1,fp))
{
return -1;
}
fclose(fp);
strcpy(users[count].username,name);
strcpy(users[count].password,psw);
count++;
return 1;
}
//函数功能:创建套接字 --> 绑定 --> 监听
void server_init()
{
//1.创建套接字
server_fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == server_fd)
error_exit("socket");
//2.初始化结构体
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(IP);
//3.绑定套接字
if(-1 == bind(server_fd,(struct sockaddr *)&addr,sizeof(addr)))
error_exit("bind");
//4.监听
if(-1 == listen(server_fd,5))
error_exit("listen");
len = sizeof(addr);
//5.初始化epoll
epfd = epoll_create(1);
if(-1 == epfd)
error_exit("epoll_create");
//6.添加server_fd 关注
ev.events = EPOLLIN;
ev.data.fd = server_fd;
if(-1 == epoll_ctl(epfd,EPOLL_CTL_ADD,server_fd,&ev))
error_exit("EPOLL_CTL_ADD");
//7.1 打开login_data
fp = fopen(PATH,"r");
if(NULL == fp)
error_exit("fopen");
//7.2 读取所有用户信息
while(fgets(buf,128,fp))
{
sscanf(buf,"%s : %s\n",users[count].username,users[count].password);
count++;
}
//7.3 关闭文件
fclose(fp);
fp = NULL;
}
/*
函数功能:通信接收函数,head+body
函数参数:接收的文件描述符
返回值 :成功返回1 / 断开连接返回0
失败返回-1
*/
int recv_data(int fd)
{
int ret = -1;
//1. 每次通信前对通信缓冲区进行清空
memset(&msg,0,sizeof(msg));
ret = recv(fd,&(msg.length),4,0);
//2.1 接收head 的 length参数
if(-1 == ret)
return -1;
if(0 == ret)
return 0;
//2.2 转端序
msg.length = ntohl(msg.length);
//3.1 接收head 的 op参数
if(-1 == recv(fd,&(msg.op),2,0))
return -1;
//3.2 转端序
msg.op = ntohs(msg.op);
//4. 接收data数据
if(-1 == recv(fd,&(msg.data),msg.length,0))
return -1;
return 1;
}
/*
函数功能:发送信息 head+body
函数参数:Client文件描述符
*/
void send_data(int fd)
{
//发送字节数
int len = msg.length + 6;
//端序处理
msg.length = htonl(msg.length);
msg.op = htons(msg.op);
send(fd,&msg,len,0);
}
int main(int argc, char *argv[])
{
//1.调用服务器初始化函数
server_init();
printf("Server is waiting for connection now...\n");
//2.开始建立通信
while(1)
{
//2 epoll_wait 处理就绪
ready = epoll_wait(epfd,evs,MAXFDS,-1);
if(-1 == ready)
{
error_exit("epoll_wait");
}
//3 处理就绪文件描述符
int i;
for(i = 0 ; i < ready ; i++)
{
if(evs[i].events == EPOLLIN)
{
//3.1 如果是服务器套接字
if(evs[i].data.fd == server_fd)
{
//3.1 建立新连接
int connfd = accept(server_fd,(struct sockaddr*)&addr,&len);
if(-1 == connfd)
error_exit("accept");
//3.2添加进epoll进行关注
ev.events = EPOLLIN;
ev.data.fd = connfd;
if(-1 == epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev))
error_exit("EPOLL_CTL_ADD");
printf("Client [%s:%d] has connected!\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
}
else
{
//4 接收客户端信息
int ret = recv_data(evs[i].data.fd);
if(-1 == ret)
{
close(evs[i].data.fd);
error_exit("recv");
continue;
}
else if(0 == ret)
{
//断开连接
if(-1 == epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,NULL))
error_exit("EPOLL_CTL_DEL");
close(evs[i].data.fd);
printf("A Client has disconnected!\n");
continue;
}
//4.1 登录操作
if(msg.op == 1)
{
char name[20] = {0};
char psw[20] = {0};
//解析用户名和密码
sscanf(msg.data,"%s : %s",name,psw);
int flag = login(name,psw);
if(-1 == flag)
{
//发送登录失败信息
strcpy(msg.data,"Server_msg : name or psw wrong.");
msg.length = strlen(msg.data);
send_data(evs[i].data.fd);
continue;
}
else
{
//发送登录成功信息
strcpy(msg.data,"Server_msg : welcome,");
strcat(msg.data,name);
msg.length = strlen(msg.data);
send_data(evs[i].data.fd);
printf("Client[%d] %s is login!\n",evs[i].data.fd,name);
continue;
}
}
//4.2 正常通信操作
else if(msg.op == 2)
{
printf("Client[%d] : %s",evs[i].data.fd,msg.data);
}
else if(msg.op == 3)
{
char name[20] = {0};
char psw[20] = {0};
//解析用户名和密码
sscanf(msg.data,"%s : %s",name,psw);
int flag = regist(name,psw);
if(0 == flag)
{
//注册失败,用户名重复
strcpy(msg.data,"Server_msg : regist failed , the username has existed!");
msg.length = strlen(msg.data);
send_data(evs[i].data.fd);
continue;
}
else if(-1 == flag)
{
//发送登录成功信息
strcpy(msg.data,"Server_msg : regist failed , unknow error!");
msg.length = strlen(msg.data);
send_data(evs[i].data.fd);
continue;
}
else if(1 == flag)
{
strcpy(msg.data,"Server_msg : regist successful!");
msg.length = strlen(msg.data);
send_data(evs[i].data.fd);
printf("Client[%d] is regist a name!\n",evs[i].data.fd);
continue;
}
}
}
}
}
}
close(server_fd);
return 0;
}
client.c
/*===============================================
* 文件名称:client.c
* 创 建 者:青木莲华
* 创建日期:2025年08月17日
* 描 述:简单登录/通信 Client(基于IPV4+TCP+epoll)
================================================*/
#include "socket_head.h"
//端口号
#define PORT 31111
//ip地址
#define IP "192.168.6.174"
//通信结构体
typedef struct
{
//head
//数据data的长度
int length;
//客户端想做的操作 :1.登录 2.正常发送数据
short op;
//body
char data[128];
}msg_t;
//通信缓冲区
msg_t msg;
//地址结构体
struct sockaddr_in addr;
//全局错误信息
char err_msg[32];
/*
函数功能:通信接收函数,head+body
函数参数:接收的文件描述符
返回值 :成功返回1 / 断开连接返回0
失败返回-1
*/
int recv_data(int fd)
{
int ret = -1;
//1. 每次通信前对通信缓冲区进行清空
memset(&msg,0,sizeof(msg));
ret = recv(fd,&(msg.length),4,0);
//2.1 接收head 的 length参数
if(-1 == ret)
return -1;
if(0 == ret)
return 0;
//2.2 转端序
msg.length = ntohl(msg.length);
//3.1 接收head 的 op参数
if(-1 == recv(fd,&(msg.op),2,0))
return -1;
//3.2 转端序
msg.op = ntohs(msg.op);
//4. 接收data数据
if(-1 == recv(fd,&(msg.data),msg.length,0))
return -1;
return 1;
}
/*
函数功能:发送信息 head+body
函数参数:Client文件描述符
*/
void send_data(int fd)
{
//发送字节数
int len = msg.length + 6;
//端序处理
msg.length = htonl(msg.length);
msg.op = htons(msg.op);
send(fd,&msg,len,0);
}
/*
函数功能:连接服务器
返回值 :成功返回 套接字文件描述符
失败返回 -1
*/
int conn()
{
//1.创建套接字 IPV4+TCP
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
strcpy(err_msg,"socket");
return -1;
}
//2.初始化结构体
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(IP);
//3. 连接
if(-1 == connect(sockfd,(struct sockaddr *)&addr,sizeof(addr)))
{
strcpy(err_msg,"connect");
close(sockfd);
return -1;
}
return sockfd;
}
int main(int argc, char *argv[])
{
int fd = conn(); //套接字文件描述符
if(-1 == fd)
{
printf("客户端错误: %s\n",err_msg);
perror(err_msg);
return -1;
}
short menu = 0; //菜单选项
char name[20] = {0}; //用户名
char psw[20] = {0}; //密码
int ret = -1;
printf("--------------菜单--------------\n");
printf("1.登录 2.发送数据 3.注册 4.退出\n");
printf("--------------------------------\n");
//通信
while(1)
{
printf("信息 : 选择菜单\n");
scanf("%hd",&menu);
//清空通信缓冲区
memset(&msg,0,sizeof(msg));
switch(menu)
{
case 1:
{
memset(name,0,sizeof(name));
memset(psw,0,sizeof(psw));
printf("请输入用户名 : ");
scanf("%19s",name);
printf("请输入密码 : ");
scanf("%19s",psw);
sprintf(msg.data,"%s : %s",name,psw);
msg.length = strlen(msg.data);
msg.op = 1;
//发送数据
send_data(fd);
//接收数据
ret = recv_data(fd);
if(-1 == ret)
{
close(fd);
perror("recv");
return -1;
}
puts(msg.data);
printf("------------------------\n");
break;
}
case 2:
{
getc(stdin);
fgets(msg.data,128,stdin);
msg.length = strlen(msg.data);
msg.op = 2;
send_data(fd);
printf("------------------------\n");
break;
}
case 3:
{
memset(name,0,sizeof(name));
memset(psw,0,sizeof(psw));
printf("请输入用户名 : ");
scanf("%19s",name);
printf("请输入密码 : ");
scanf("%19s",psw);
sprintf(msg.data,"%s : %s",name,psw);
msg.length = strlen(msg.data);
msg.op = 3;
//发送数据
send_data(fd);
//接收数据
ret = recv_data(fd);
if(-1 == ret)
{
close(fd);
perror("recv");
return -1;
}
puts(msg.data);
printf("------------------------\n");
break;
}
case 4:
{
close(fd);
printf("信息 : 退出成功\n");
return 0;
}
}
}
return 0;
}
运行截图