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的調用,提高應用程序效率。
-
調用epoll_create建立一個epoll對象(在epoll文件系統中給這個句柄分配資源);
-
調用epoll_ctl向epoll對象中添加這所有連接的套接字;
-
調用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。