C语言--socket、epoll

三次握手及四次挥手

Socket 起源于 Unix,而Unix基本哲学之一就是一切皆文件,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符

通信模型:
在这里插入图片描述

Socket三次握手及四次挥手过程:

tcp标志位说明:
SYN(synchronous建立联机)
ACK(acknowledgement 确认)
PSH(push传送)
FIN(finish结束)
RST(reset重置)
URG(urgent紧急)
在这里插入图片描述

三次握手过程:
(1)第一次握手:客户端向服务器发送SYN包,假设序列号为x,进入SYN_SEND状态。
(2)第二次握手:服务器收到SYN包,进行确认,发送ACK报文,序列号为x+1,同时自己也发送一个SYN包(假设序列号为y),此时服务器进入SYN_RECV状态。
(3)第三次握手:客户端收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(确认号为y+1),客户端和服务器进入ESTABLISHED状态,完成三次握手。

四次挥手过程:
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
(3)然后服务器B也会发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,确认序号为收到序号加1。

主动关闭连接的一方收到FIN后会进入TIME_WAIT状态。这样设计主要有两个原因:
(1)可靠地实现TCP连接的终止。四次挥手过程中,如果客户端最终发送的ACK丢失,服务器会重发FIN,通过保持TIME_WAIT状态可以允许它重发ACK,保证TCP连接正常地终止。
(2)让旧连接的重复分组在网络中消失。因为如果复用旧连接的端口马上开启一个新的连接,原来旧连接中的数据包可能会被这个新连接收到。处于TIME_WAIT状态的连接就可以避免这种情况。它会持续2个最大分解生命期(MSL),也就是一个IP数据包能在网络中生存的最长时间,保证新连接建立的时候,旧连接的数据包已经在网络中消失了。

Socket C示例

接口说明:

/************************************************************************************************************************
1、int socket(int family,int type,int protocol)
family:
    指定使用的协议簇:AF_INET(IPv4) AF_INET6(IPv6) AF_LOCAL(UNIX协议) AF_ROUTE(路由套接字) AF_KEY(秘钥套接字)。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:
    指定使用的套接字的类型:SOCK_STREAM(字节流套接字) SOCK_DGRAM
protocol:
    如果套接字类型不是原始套接字,那么这个参数就为0
    
2、int bind(int sockfd, struct sockaddr *myaddr, int addrlen)
sockfd:
    socket函数返回的套接字描述符
myaddr:
    是指向本地IP地址的结构体指针
myaddrlen:
    结构长度
struct sockaddr{
    unsigned short sa_family; //通信协议类型族AF_xx
    char sa_data[14];  //14字节协议地址,包含该socket的IP地址和端口号
};
struct sockaddr_in{
    short int sin_family; //通信协议类型族
    unsigned short int sin_port; //端口号
    struct in_addr sin_addr; //IP地址
    unsigned char si_zero[8];  //填充0以保持与sockaddr结构的长度相同

3、int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen)
sockfd:
    socket函数返回套接字描述符
serv_addr:
    服务器IP地址结构指针
addrlen:
    结构体指针的长度
    
4、int listen(int sockfd, int backlog)
sockfd:
    socket函数绑定bind后套接字描述符
backlog:
    设置可连接客户端的最大连接个数,当有多个客户端向服务器请求时,收到此值的影响。默认值20
    
5、int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen)
sockfd:
    socket函数经过listen后套接字描述符
cliaddr:
    客户端套接字接口地址结构
addrlen:
    客户端地址结构长度
    
6、int send(int sockfd, const void *msg,int len,int flags)
7、int recv(int sockfd, void *buf,int len,unsigned int flags)
sockfd:
    socket函数的套接字描述符
msg:
    发送数据的指针
buf:
    存放接收数据的缓冲区
len:
    数据的长度,把flags设置为0
*************************************************************************************************************************/

sockaddr_in

socket分为服务器端和客户端,服务器端监听端口发来的请求,收到后向客户端发送一个Hello World,客户机负责发送消息并打印收到的Hello World.

服务器步骤:建立socket,绑定socket和地址信息,开启监听,收到请求后发送数据。

客户端步骤:建立socket,连接服务器端,接收并打印服务器给的数据

服务器:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>

#define PORT 1500//端口号 
#define BACKLOG 5/*最大监听数*/ 

int main(){
	int sockfd,new_fd;/*socket句柄和建立连接后的句柄*/
	struct sockaddr_in my_addr;/*本方地址信息结构体,下面有具体的属性赋值*/
	struct sockaddr_in their_addr;/*对方地址信息*/
	int sin_size;

	sockfd=socket(AF_INET,SOCK_STREAM,0);//建立socket 
	if(sockfd==-1){
		printf("socket failed:%d",errno);
		return -1;
	}
	my_addr.sin_family=AF_INET;/*该属性表示接收本机或其他机器传输*/
	my_addr.sin_port=htons(PORT);/*端口号*/
	my_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*IP,括号内容表示本机IP*/
	bzero(&(my_addr.sin_zero),8);/*将其他属性置0*/
	if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0){//绑定地址结构体和socket
		printf("bind error");
		close(sockfd);
		return -1;
	}
    listen(sockfd,BACKLOG);//开启监听 ,第二个参数是最大监听数 
    while(1){
    	sin_size=sizeof(struct sockaddr_in);
    	new_fd=accept(sockfd,(struct sockaddr*)&their_addr,&sin_size);//在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小 
    	if(new_fd==-1){
    		printf("receive failed");
		} else{
			printf("receive success");
			send(new_fd,"Hello World!",12,0);//发送内容,参数分别是连接句柄,内容,大小,其他信息(设为0即可) 
		}
	}
	return 0;
} 

客户端:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>

 
#define DEST_PORT 1500//目标地址端口号 
#define DEST_IP "127.0.0.1"/*目标地址IP,这里设为本机*/ 
#define MAX_DATA 100//接收到的数据最大程度 

int main(){
	int sockfd,new_fd;/*cocket句柄和接受到连接后的句柄 */
	struct sockaddr_in dest_addr;/*目标地址信息*/
	char buf[MAX_DATA];//储存接收数据 

	sockfd=socket(AF_INET,SOCK_STREAM,0);/*建立socket*/
	if(sockfd==-1){
		printf("socket failed:%d",errno);
	}

	//参数意义见上面服务器端 
	dest_addr.sin_family=AF_INET;
 	dest_addr.sin_port=htons(DEST_PORT);
	dest_addr.sin_addr.s_addr=inet_addr(DEST_IP);
	bzero(&(dest_addr.sin_zero),8);
	
	if(connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr))==-1){//连接方法,传入句柄,目标地址和大小 
		printf("connect failed:%d",errno);//失败时可以打印errno 
		close(sockfd);
		return -1;
	} else{
		printf("connect success");
		recv(sockfd,buf,MAX_DATA,0);//将接收数据打入buf,参数分别是句柄,储存处,最大长度,其他信息(设为0即可)。 
		printf("Received:%s",buf);
	}
	close(sockfd);//关闭socket 
	return 0;
} 

sockaddr_un

进程间通信的另一种方式是使用UNIX套接字,人们在使用这种方式时往往用的不是网络套接字,而是一种称为本地套接字的方式。这样做可以避免为黑客留下后门。socket接口中的协议族参数决定了使用的地址类型,AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合,而AF_UNIX决定要用一个绝对路径名作为地址。

服务端:

#include<stdio.h> 
#include<stdlib.h> 
#include<errno.h> 
#include<string.h> 
#include<sys/types.h>
#include<netinet/in.h> 
#include<sys/socket.h> 
#include<sys/wait.h> 
#include<sys/un.h>

#define BACKLOG 5/*最大监听数*/ 
#define PATH "un.sock"

int main()
{ 
	int fd,new_fd;/*socket句柄和建立连接后的句柄*/ 
	struct sockaddr_un my_addr;/*本方地址信息结构体,下面有具体的属性赋值*/ 
	struct sockaddr_un their_addr;/*对方地址信息*/ 
	int size; 

	fd=socket(AF_UNIX,SOCK_STREAM,0);//建立socket 
	if (fd == -1) { 
		printf("socket failed:%d\n",errno); 
		return -1; 
	}
 
	unlink(PATH);
	memset(&my_addr, 0, sizeof(my_addr));
	my_addr.sun_family = AF_UNIX;	
	strcpy(my_addr.sun_path, PATH);

	if (bind(fd,(struct sockaddr*)&my_addr, sizeof(struct sockaddr)) < 0){//绑定地址结构体和socket 
		printf("bind error: %d\n", errno); 
		goto END; 
	}
	listen(fd,BACKLOG);//开启监听 ,第二个参数是最大监听数 
	while (1) { 
		printf("wait accept...\n"); 
		size=sizeof(struct sockaddr_un); 
		new_fd=accept(fd,(struct sockaddr*)&their_addr,&size);//在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小 	
		if(new_fd==-1){ 
			printf("receive failed\n"); 
		} else{ 
			printf("receive success. fd=%d, new_fd=%d\n", fd, new_fd); 
			send(new_fd,"Hello World!\n",13,0);//发送内容,参数分别是连接句柄,内容,大小,其他信息(设为0即可) 
		} 
	} 
END:
	close(fd);
	return 0; 
} 

客户端:

#include<stdio.h> 
#include<stdlib.h> 
#include<errno.h> 
#include<string.h> 
#include<sys/types.h>
#include<netinet/in.h> 
#include<sys/socket.h> 
#include<sys/wait.h> 
#include<sys/un.h>

#define PATH "un.sock"
#define MAX_DATA 100//接收到的数据最大程度 

int main()
{ 
	int fd;/*socket句柄和建立连接后的句柄*/ 
	struct sockaddr_un s_addr;/*对方地址信息*/ 
	char buf[MAX_DATA];//储存接收数据 
	int buf_len;
 
	fd=socket(AF_UNIX,SOCK_STREAM,0);//建立socket 
	if (fd == -1) { 
		printf("socket failed:%d\n",errno); 
		return -1; 
	}

	memset(&s_addr, 0, sizeof(s_addr));
	s_addr.sun_family = AF_UNIX;	
	strcpy(s_addr.sun_path, PATH);

	if (connect(fd, (struct sockaddr*)&s_addr, sizeof(struct sockaddr)) < 0){//绑定地址结构体和socket 
		printf("connect error: %d\n", errno); 
		goto END; 
	} else {
		printf("connect success, fd=%d\n", fd);
        recv(fd,buf,MAX_DATA,0);//将接收数据打入buf,参数分别是句柄,储存处,最大长度,其他信息(设为0即可)。
        printf("Received:%s",buf);
	}
END:	
	close(fd);
	return 0; 
} 

socket分为阻塞和非阻塞两种。阻塞式的socket的recv服从这样的规则:当缓冲区内有数据时,立即返回所有的数据;当缓冲区内无数据时,阻塞直到缓冲区中有数据。非阻塞式的socket的recv服从的规则则是:当缓冲区内有数据时,立即返回所有的数据;当缓冲区内无数据时,产生EAGAIN的错误并返回(在Python中会抛出一个异常)。两种情况都不会返回空字符串,返回空数据的结果是对方关闭了连接之后才会出现的。下面的示例中会添加set_nonblock(fcntl函数)实现非阻塞socket。

select、poll和epoll

如何解决服务器多并发问题?
1、每个进程处理一个客户端
2、每个线程处理一个客户端
3、io复用模型

select、poll、epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。io复用模型只需要一个进程就够了,之所以能够同时处理多个客户端的请求,原因是可以查询哪个客户端准备好了,对于准备好的客户端(例如客户端已经发了信息过来,本服务器用read读取数据的时候不会阻塞;另外,客户端已经关闭了连接,那么本服务read的时候,返回0,也不会阻塞),则和它进行通信,而未准备好的,就暂时先不理会。

1,select 实现

select函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
//nfds: 最大的那个fd整数加1。
//fd_set: fd的集合。三个集合,分别是读、写、异常的集合,当置为空时表示用不到。可以将关心的fd放到对应的集合里去,然后交给内核去轮询。
//timeout: 代表超时时间,是struct timeval类型的结构体,参数有:long tv_sec(秒) 和long tv_usec(微秒),为空表示永不超时。

fd集合的操作:
void FD_CLR(int fd,fd_set *set);//将集合里对应的fd清掉
void FD_SET(int fd,fd_set *set);//将fd加到集合里
int FD_IS SET(int fd,fd_set *set);//是否准备好了,好了就返回非零值
void FD_ZERO(fd_set*set);//集合全部清空

select的几大缺点:
(1)每次调用select都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024

2, poll 实现

poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。

3、epoll 实现

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生:

  1. 调用epoll_create建立一个epoll对象(在epoll文件系统中给这个句柄分配资源);

  2. 调用epoll_ctl向epoll对象中添加这所有连接的套接字;

  3. 调用epoll_wait收集发生事件的连接。

epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?

对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果)。

对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max 查看,一般来说这个数目和系统内存关系很大。

总结:epoll采用事件触发的方式,当某个fd准备好后会触发事件,这样减少了内核的轮询。同时,epoll返回的是那些准备好的fd,避免程序员进行全部的轮询。另外,select的fd数上限一般是1024,但是epoll没有上限。

epoll函数原型:

1int epoll_create(int size); 
创建一个epoll接口,返回一个epoll描述符,后面要用到。

2int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
用来从epoll接口中添加fd或删除、修改fd。
(1)epfd是接口描述符,
(2)op是添加、修改、删除三者,fd是要操作的对象,event是监视的事件。结构如下:
typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;
           
           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
           
3int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);1)events是输出型的参数,返回的是准备好的描述符,
(2)maxevents是事件数量的上限
(3)timeout是超时,以毫秒为单位,-1代表用不超时,0代表不等待立即返回。

epoll的水平触发和边沿触发?

水平触发:epoll的事件默认情况下是水平触发。例如,只要客户端的数据仍然未读完,那么事件就会一直发生。告诉服务器,请将数据读出来。

边沿触发:加上了events那里加了EPOLLET这个选项后,变成边沿触发。也就是数据可读,则只触发一次事件,服务器必须一直读,直到把数据读完。

服务端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/un.h>
 
#define MAX_FD_NUM 3
#define PATH "un.sock"
 
void setnonblock(int fd) {
    int flag = fcntl(fd, F_GETFL, 0);
    if (flag == -1) {
        printf("get fcntl flag %s\n", strerror(errno));
        return;
    }
    int ret = fcntl(fd, F_SETFL, flag | O_NONBLOCK);
    if (ret == -1) {
        printf("set fcntl non-blocking %s\n", strerror(errno));
        return;
    }
}
 
int socket_create() {
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd == -1) {
        printf("socket create %s\n", strerror(errno));
        return -1;
    }
    setnonblock(fd);
    struct sockaddr_un addr;

    unlink(PATH);
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, PATH);

    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
    	printf("socket bind: %s\n", strerror(errno));
        return -1;
    }
    if (listen(fd, 20) == -1) {
        printf("socket listen %s\n", strerror(errno));
        return -1;
    }
    return fd;
}
 
void socket_accept(int fd) {
    struct epoll_event event, events[MAX_FD_NUM];
    int client_fd;
    int epfd = epoll_create(MAX_FD_NUM);
    if (epfd == -1) {
        printf("epoll create %s\n", strerror(errno));
        return;
    }
    
    memset(&event, 0, sizeof(event));
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLERR | EPOLLHUP;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
        printf("epoll ctl %s\n", strerror(errno));
        return;
    }
    while (1) {
        int num = epoll_wait(epfd, events, MAX_FD_NUM, -1);
        if (num == -1) {
            printf("epoll wait %s\n", strerror(errno));
            break;
        } else {
            int i = 0;
            for (; i<num; ++i) {
                if (events[i].data.fd == fd) {
                    struct sockaddr_un client_addr;
                    memset(&client_addr, 0, sizeof(client_addr));
                    int len = sizeof(client_addr);
                    client_fd = accept(fd, (struct sockaddr *)&client_addr, &len);
                    if (client_fd == -1) {
                        printf("socket accept %s\n", strerror(errno));
                        return;
                    } else {
                        printf("socket accept success. fd=%d, client_fd=%d\n", fd, client_fd);
		    }
                    setnonblock(client_fd);
                    event.data.fd = client_fd;
                    event.events = EPOLLIN | EPOLLERR | EPOLLHUP;
                    if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
                        printf("epoll ctl %s\n", strerror(errno));
                        return;
                    }
                    continue;
                } else if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP) {
                    printf("epoll err\n");
                    close(events[i].data.fd);
                    continue;
                } else {
                    char buf[64];
                    memset(buf, 0, sizeof(buf));
                    recv(events[i].data.fd, buf, sizeof(buf), 0);
                    printf("recv msg: %s", buf);
                    close(events[i].data.fd);
                    continue;
                }
            }
        }
    }
}
 
int main(int argc, char *argv[]) {
    int fd = socket_create();
    if (fd == -1) {
        printf("socket create fd failed\n");
        return;
    }
    socket_accept(fd);
    return 0;
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
 
#define PATH "un.sock"

int socket_connect() {
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd == -1) {
        printf("socket create %s\n", strerror(errno));
        return -1;
    }
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, PATH);
    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        printf("socket connect %s\n", strerror(errno));
        return -1;
    }
    return fd;
}
 
void socket_send(int fd) {
    char buf[]="Hello Server!\n";
    send(fd, buf, strlen(buf), 0);
    printf("send msg: %s\n", buf);
    close(fd);
}
 
int main(int argc, char *argv[]) {
    int fd = socket_connect();
    if (fd == -1) {
        printf("client socket create failed\n");
        return;
    } else {
		printf("socket connect success!, fd=%d\n", fd);
    }
    socket_send(fd);
    return 0;
}


ps:
O_NONBLOCK和O_NDELAY所产生的结果都是使I/O变成非阻塞模式(non-blocking),在读取不到数据或是写入缓冲区已满会马上return,而不会阻塞等待。

它们的差别在于:在读操作时,如果读不到数据,O_NDELAY会使I/O函数马上返回0,但这又衍生出一个问题,因为读取到文件末尾(EOF)时返回的也是0,这样无法区分是哪种情况。因此,O_NONBLOCK就产生出来,它在读取不到数据时会回传-1,并且设置errno为EAGAIN。

send,sendto以及sendmsg

socket编程读写文件recv()、send()函数返回值分析

ssize_t send (int s, const void *msg, size_t len, int flags);
ssize_t sendto (int s, const void *msg, size_t len, int flags, const struct sockaddr *to,
socklen_t tolen);
ssize_t sendmsg (int s, const struct msghdr *msg, int flags);

send,sendto以及sendmsg系统调用用于发送消息到另一个套接字。send函数在套接字处于连接状态时方可使用。而sendto和sendmsg在任何时候都可使用 。

下面的代码片段演示了send,sendto,sendmsg函数的用法:

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

void Send()
{
struct sockaddr_in serv_addr;
int sock_fd;
char line[15] = "Hello world!";
int size = 13;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
serv_addr.sin_port = htons(5000);
sock_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
connect(sock_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
send(sock_fd, line, size, 0);
close(sock_fd);
}

void Sendto()
{
sockaddr_in receiver_addr;
int sock_fd;
char line[15] = "Hello World!";
sock_fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
receiver_addr.sin_family = AF_INET;
receiver_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
receiver_addr.sin_port = htons(5000);
sendto(sock_fd, line, 13, 0,(struct sockaddr*)&receiver_addr,sizeof(receiver_addr));
close(sock_fd);
}

void sendmsg()
{
struct sockaddr_in receiver_addr;
int sock_fd;
char line[15] = "Hello World!";
struct msghdr msg;
struct iovec iov;
sock_fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
receiver_addr.sin_family = AF_INET;
receiver_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
receiver_addr.sin_port = htons(5000);
msg.msg_name = &receiver_addr;
msg.msg_namelen = sizeof(receiver_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_iov->iov_base = line;
msg.msg_iov->iov_len = 13;
msg.msg_control = 0;
msg.msg_controllen = 0;
msg.msg_flags = 0;
sendmsg(sock_fd,&msg,0);
close(sock_fd);
}


  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中使用epoll机制编写客户端代码如下: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/epoll.h> #define MAX_EVENTS 100 #define MAX_BUF_SIZE 1024 int main(int argc, char** argv) { int sockfd, epollfd; struct epoll_event event, events[MAX_EVENTS]; // 创建socket if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 设置服务器地址 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); if (inet_pton(AF_INET, "127.0.0.1", &(server_addr.sin_addr)) <= 0) { perror("invalid address"); exit(EXIT_FAILURE); } // 连接服务器 if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("connection failed"); exit(EXIT_FAILURE); } // 创建epoll实例 if ((epollfd = epoll_create1(0)) < 0) { perror("epoll creation failed"); exit(EXIT_FAILURE); } // 设置监听事件 event.events = EPOLLIN; event.data.fd = sockfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) < 0) { perror("epoll control failed"); exit(EXIT_FAILURE); } char buf[MAX_BUF_SIZE]; while (1) { int num_events = epoll_wait(epollfd, events, MAX_EVENTS, -1); for (int i = 0; i < num_events; i++) { if (events[i].data.fd == sockfd) { // 从服务器接收数据 memset(buf, 0, sizeof(buf)); ssize_t recv_len = recv(sockfd, buf, sizeof(buf), 0); if (recv_len < 0) { perror("receive data failed"); exit(EXIT_FAILURE); } else if (recv_len == 0) { printf("Connection closed by server\n"); exit(EXIT_SUCCESS); } else { printf("Received message from server: %s", buf); } } } } close(sockfd); close(epollfd); return 0; } ``` 以上代码实现了一个简单的epoll客户端,将连接到服务器的socket加入到epoll事件中进行监听。当有事件触发时,通过recv函数接收服务器发送的数据,并打印到控制台上。同时,代码还对错误情况进行了处理,比如socket创建失败、连接失败等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值