服务端
#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。