服务端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/epoll.h>
typedef struct sockaddr* (SA);
int add_fd(int epfd,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
if(-1 == ret)
{
perror("add fd");
}
return ret;
}
int del_fd(int epfd,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret = epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev);
if(-1 == ret)
{
perror("add fd");
}
return ret;
}
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM,0 );
if(-1 ==listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr =inet_addr("127.0.0.1");
//man 7 socket
int on = 1;
setsockopt(listfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
setsockopt(listfd,SOL_SOCKET,SO_REUSEPORT,&on,sizeof(on));
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 ==ret)
{
perror("bind");
exit(1);
}
//建立连接的排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
struct epoll_event rev[10]={0};
//1 create set
int epfd = epoll_create(10);
if(-1 == epfd)
{
perror("epoll_create");
return 1;
}
// 2 .add fd
add_fd(epfd,listfd);
int num = 0;
while(1)
{
//3 wait event
int ep_ret = epoll_wait(epfd,rev,10,-1);
int i = 0 ;
//4 find fd handle
for(i = 0 ;i<ep_ret;i++)
{
if(rev[i].data.fd == listfd)
{ //通讯套接字
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
continue;
}
add_fd(epfd,conn);
num++;
char clientIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(cli.sin_addr), clientIP, INET_ADDRSTRLEN);
int clientPort = ntohs(cli.sin_port);
printf("cli connected. ip: %s, port: %d. total clis: %d\n", clientIP, clientPort, num);
}
else
{
int conn = rev[i].data.fd;
char buf[512]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(rd_ret<=0)
{
del_fd(epfd,conn);
close(conn);
num--;
printf("cli off line! total clis:%d\n",num);
break;
}
time_t tm;
time(&tm);
sprintf(buf,"%s %s",buf,ctime(&tm));
send(conn,buf,strlen(buf),0);
}
}
}
close(listfd);
return 0;
}
初始化与套接字创建
listfd = socket(AF_INET,SOCK_STREAM,0);
创建一个TCP套接字
bind(listfd,(SA)&ser,sizeof(ser));
将服务器的IP地址和端口绑定到创建的套接字上
listen(listfd,3);
将套接字设置为监听模式,最多可处理3个连接排队
setsockopt
是一个用于设置套接字选项的系统调用函数,配置套接字的各种行为和特性
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd
:套接字文件描述符,即你要配置的套接字。
level
:指定要设置的选项所在的协议层。常见的值有:
SOL_SOCKET
:用于设置套接字层的选项。IPPROTO_TCP
:用于设置TCP协议层的选项(如TCP_NODELAY
)。IPPROTO_IP
:用于设置IP协议层的选项(如IP_TTL
)。
optname
:要设置的选项名,取决于level
。例如:
- 对于
SOL_SOCKET
层,常见的选项包括:
SO_REUSEADDR
:允许重用本地地址。SO_KEEPALIVE
:启用TCP连接的保活机制。- 对于
IPPROTO_TCP
层,常见的选项包括:
TCP_NODELAY
:禁用Nagle算法,提高数据传输的实时性。
optval
:指向要设置的选项值的指针。它的类型和长度取决于optname
。
optlen
:optval
指向的数据长度。
int on = 1;
setsockopt(listfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
setsockopt(listfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
int on = 1;
这一行代码的作用是设置一个整型变量on
的值为1
,通常用于启用某些套接字选项。初始化一个整型变量on
,并将其值设置为1
。这个值通常用作标志,表示在调用setsockopt
函数时,某些套接字选项应该被启用或激活。这里,
on
的值1
被用来启用SO_REUSEADDR
和SO_REUSEPORT
这两个选项。具体说明如下:
SO_REUSEADDR
:允许重用本地地址。在绑定地址和端口时,如果这个地址和端口处于TIME_WAIT
状态(例如,前一个连接已经关闭),仍然可以重新绑定。
SO_REUSEPORT
:允许多个套接字绑定到相同的端口。这对于提高多进程或多线程服务器的性能很有用,因为它允许多个进程共享同一个端口。
epoll的创建与配置
epoll_create(10);
创建一个epoll实例,用于管理多个文件描述符。
add_fd(epfd,listfd);
将监听套接字添加到epoll实例中,以便在有连接到来时可以被检测到。
事件循环
epoll_wait(epfd,rev,10,-1);
等待有事件发生,
rev
数组保存触发的事件。
处理连接事件
accept(listfd,(SA)&cli,&len);
:接受新的客户端连接,并将其套接字添加到epoll实例中。- 记录并显示新连接的客户端IP和端口信息。
处理数据事件
recv(conn,buf,sizeof(buf),0);
:从客户端接收数据。- 如果接收到的数据为空或出错,表示客户端断开连接,从epoll实例中移除并关闭套接字。
- 如果接收到数据,服务器会在数据后附加当前时间并发送回客户端。
客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/time.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
int conn= socket(AF_INET,SOCK_STREAM,0);
if(-1 == conn)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr =inet_addr("127.0.0.1");
int ret = connect(conn,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
int i =5;
struct timeval tv;
tv.tv_sec = 3;
tv.tv_usec = 0 ;
setsockopt(conn,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)); //recv == -1 timeout
while(1)
{
char buf[512]="hello,this tcp test";
send(conn,buf,strlen(buf),0);
bzero(buf,sizeof(buf));
int ret = recv(conn,buf,sizeof(buf),0);
if(ret==0)
{
printf("ser close\n");
break;
}
if(ret<=0)
{
printf("time out,contineu\n");
}
printf("ser:%s\n",buf);
sleep(1);
}
close(conn);
return 0;
}
初始化与套接字创建
conn= socket(AF_INET,SOCK_STREAM,0);
创建一个TCP套接字。
connect(conn,(SA)&ser,sizeof(ser));
连接到服务器指定的IP地址和端口。
超时设置
setsockopt(conn,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));
设置套接字的接收超时时间为3秒。如果在3秒内没有接收到数据,
recv
将返回-1,并超时。
通信循环
- 客户端通过
send
函数向服务器发送字符串"hello,this tcp test"。recv(conn,buf,sizeof(buf),0);
:等待服务器的响应。
- 如果超时,则输出"time out, continue"。
- 如果接收到数据,则输出服务器的响应内容。
函数
send
send
函数用于在网络编程中通过套接字发送数据。它常用于TCP/IP套接字通信中,将数据从客户端发送到服务器或从服务器发送到客户端。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd
:
- 要发送数据的套接字文件描述符。这个描述符应是一个有效且已连接的套接字。
buf
:
- 指向包含要发送数据的缓冲区的指针。数据应存储在内存中,类型为
const void *
。
len
:
- 要从缓冲区中发送的字节数。表示需要传输的数据长度。
flags
:
- 用于修改
send
函数行为的标志。常见的标志包括:
0
:默认行为。MSG_OOB
:发送带外数据(通常不用于常规TCP数据)。MSG_DONTWAIT
:非阻塞模式发送数据,如果发送操作会阻塞,则立即返回。
recv
recv
函数用于从套接字接收数据,是网络编程中一个重要的系统调用。它通常用于接收客户端或服务器发送的数据。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
:
- 套接字文件描述符,必须是一个有效的已连接套接字。
buf
:
- 指向存储接收到数据的缓冲区的指针。接收到的数据会被放入这个缓冲区中。
len
:
- 缓冲区
buf
的大小(以字节为单位)。这是recv
函数可以读取的最大字节数。
flags
:
- 用于修改
recv
函数的行为的标志。常见的标志包括:
0
:默认行为。MSG_OOB
:接收带外数据(通常不用于常规TCP数据)。MSG_PEEK
:查看数据但不移除数据。可以用于检查数据而不从队列中移除它。
epoll_ctl
epoll_ctl
函数是 Linux 下用于管理 epoll
实例的系统调用。它允许在 epoll
实例中添加、修改或删除事件。epoll
是一种高效的 I/O 事件通知机制,适用于处理大量并发连接的网络应用。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd
:
epoll
实例的文件描述符。它是通过调用epoll_create
或epoll_create1
创建的。
op
:
- 操作类型,指定要对
epfd
执行的操作。这可以是以下值之一:
EPOLL_CTL_ADD
:将一个文件描述符添加到epoll
实例中。EPOLL_CTL_MOD
:修改已经添加到epoll
实例中的文件描述符的事件。EPOLL_CTL_DEL
:从epoll
实例中删除一个文件描述符。
fd
:
- 要管理的文件描述符。它是需要添加、修改或删除的文件描述符(如套接字描述符)。
event
:
- 指向
epoll_event
结构体的指针,该结构体描述了感兴趣的事件。
epoll_event
结构体
struct epoll_event {
uint32_t events; // 事件标志
epoll_data_t data; // 用户数据
};
events
:- 指定感兴趣的事件,如
EPOLLIN
(可读事件)、EPOLLOUT
(可写事件)、EPOLLERR
(错误事件)等。
- 指定感兴趣的事件,如
data
:- 用户定义的数据,可以用于存储与文件描述符相关的附加信息。通常为
epoll_data_t
类型。
- 用户定义的数据,可以用于存储与文件描述符相关的附加信息。通常为
epoll_wait
epoll_wait
函数用于等待 epoll
实例中监控的文件描述符发生事件。这是 epoll
机制的核心部分,它会阻塞程序直到一个或多个事件发生,或者直到超时。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epfd
:
epoll
实例的文件描述符,通常通过epoll_create
或epoll_create1
创建得到。
events
:
- 指向
epoll_event
结构体数组的指针,用于存储发生的事件信息。epoll_wait
将填充这个数组以通知哪些文件描述符上有事件发生。
maxevents
:
events
数组的大小,表示epoll_wait
可以返回的最大事件数。即events
数组能够存储的最大事件数量。
timeout
:
- 等待事件的超时时间,单位为毫秒。可以是以下值之一:
-1
:无限期等待,直到有事件发生。0
:立即返回,不阻塞。如果没有事件立即发生,则返回0
。- 大于
0
的值 :等待指定的毫秒数。如果在指定时间内有事件发生,则返回;否则返回0
。