C語言--socket、epoll

sockaddr_in

分爲服務器端和客戶端,服務器端監聽端口發來的請求,收到後向客戶端發送一個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;
} 

說明:

/************************************************************************************************************************
1、int socket(int family,int type,int protocol)
family:
    指定使用的協議簇:AF_INET(IPv4) AF_INET6(IPv6) AF_LOCAL(UNIX協議) AF_ROUTE(路由套接字) AF_KEY(祕鑰套接字)
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_un

進程間通信的另一種方式是使用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);
		send(fd,"Hello World!\n",13,0);
	}
END:	
	close(fd);
	return 0; 
} 

epoll

epoll是Linux內核爲處理大批量文件描述符而作了改進的poll,是Linux下多路複用IO接口select/poll的增強版本,它能顯著提高程序在大量併發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。

  1. 調用epoll_create建立一個epoll對象(在epoll文件系統中給這個句柄分配資源);

  2. 調用epoll_ctl向epoll對象中添加這所有連接的套接字;

  3. 調用epoll_wait收集發生事件的連接。

這樣只需要在進程啓動時建立1個epoll對象,並在需要的時候向它添加或刪除連接就可以了,因此,在實際收集事件時,epoll_wait的效率就會非常高,因爲調用epoll_wait時並沒有向它傳遞這100萬個連接,內核也不需要去遍歷全部的連接。

服務端:

#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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章