网络 IPC 套接字socket
APUE書中所有實例源碼下載地址:http://www.apuebook.com
apue學習筆記(第十六章 網絡IPC:套接字):https://www.cnblogs.com/runnyu/p/4648678.html
一起學 Unix 環境高級編程 (APUE) 之 網絡 IPC:套接字:http://www.cnblogs.com/0xcafebabe/p/4478824.html
linux下網絡編程:http://blog.csdn.net/freeking101/article/details/71449111
根據 APUE (第十六章 網絡IPC:套接字) 整理,如有疑問,直接看 APUE。。。
1. 套接字描述符
socket 函數
使用 man socket 查看具體使用
NAMEsocket - create an endpoint for communicationSYNOPSIS#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol);DESCRIPTIONsocket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint.The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currentlyopen for the process.
2. 地址
字節序
IP地址+端口?可以唯一確定一個socket進程。運行在同一計算機上進程之間相互通信時。一般不用考慮字節的順序(字節序分為大端序和小端序。小端序:低地址存放數據低位,高地址存放數據高位,可以記憶為:小弟弟(低低),小高高。雖說有點黃但好記,大端序和小端序存儲相反)。字節序是一個處理器架構特性,用于指示像整數這樣的大數據類型的內部字節順序。不同架構的處理器可能是大端也可能是小端,有些處理器可以配置成大端或者小端。字節序不一樣,數據就不能正常處理。
大端格式:低地址存放高位數據,高地址存放低位數據。 小端格式:低地址存放低位數據,高地址存放高位數據。
如圖所示,假設要存放的數據是 0x30313233,那么 33 是低位,30 是高位,在大端存儲格式中,30 存放在低位,33 存放在高位;而在小端存儲格式中,33 存放在低位,30 存放在高位。這個東西有什么作用呢?它其實就是我們使用的網絡設備(計算機、平板電腦、智能手機等等)在內存當中存儲數據的格式。
處理器字節序和網絡序轉換函數
既然通訊雙方的設備存儲數據的格式不同,那么一端發送過去的數據,另一端是無法正確解析的,這該怎么辦?沒關系,其實系統準備了一組函數可以實現字節序轉換,我們可以像使用公式一樣使用它們。
地址格式
二進制地址和點分十進制字符串地址轉換
地址內部結構信息查詢
相關函數(可以使用 man 命令查看函數的詳細使用幫助):
get network host entry (得到給定計算機的主機信息)gethostbyname, gethostbyaddr, sethostent, gethostent, endhostent, h_errno, herror, hstrerror, gethostbyaddr_r,gethostbyname2, gethostbyname2_r, gethostbyname_r, gethostent_r get network entry (得到網絡和網絡號相關信息)getnetent, getnetbyname, getnetbyaddr, setnetent, endnetentget protocol entry (得到協議和協議號相關信息)getprotoent, getprotobyname, getprotobynumber, setprotoent, endprotoent get service entry (得到服務相關信息。服務是由地址的端口號部分表示的)getservent, getservbyname, getservbyport, setservent, endserventnetwork address and service translation (網絡地址和服務映射)getaddrinfo, freeaddrinfo, gai_strerror
示例 使用getaddrinfo 打印主機和服務信息
使用到的數據結構
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; }; struct sockaddr_in { sa_family_t sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct in_addr { __be32 s_addr; };hint是用于過濾地址的模版。只選取ai_flags,ai_type,ai_family,ai_protocol,用來指定如何處理地址和名字。
addrgetinfo()取得輸入主機和服務的 addrinfo解構,按照hint的過濾作用保存在ailist中,依次打印flag,type等,在通過定義struct sockaddr_in snip來保存AF_INET family 這一簇地址格式的 aip->ai_addr;使用inet_ntop()函數把得到的地址信息解析出來,放到addr指針域,打印出來。
代碼如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <netdb.h> #include <sys/socket.h>void print_family(struct addrinfo *aip) {printf("family ");switch(aip->ai_family){case AF_INET:printf("inet ");break;case AF_INET6:printf("inet6 ");break;case AF_UNIX:printf("unix ");break;case AF_UNSPEC:printf("unspecified ");break;default:printf("unkown ");} }void print_type(struct addrinfo *aip) {printf("type ");switch(aip->ai_socktype){case SOCK_STREAM:printf("stream ");break;case SOCK_DGRAM:printf("datagram");break;case SOCK_SEQPACKET:printf("seqpacket ");break;case SOCK_RAW:printf("raw ");break;default:printf("unknown (%d)",aip->ai_socktype);} }void print_protocol(struct addrinfo *aip) {printf(" protocol ");switch(aip->ai_protocol){case 0:printf("default ");break;case IPPROTO_TCP:printf("TCP ");break;case IPPROTO_UDP:printf("UDP ");break;case IPPROTO_RAW:printf("raw ");break;default:printf("unknown (%d)",aip->ai_protocol);} }void print_flags(struct addrinfo *aip) {printf("flags");if(aip->ai_flags == 0)printf("0");else{if(aip->ai_flags & AI_PASSIVE)printf(" passive ");if(aip->ai_flags & AI_CANONNAME)printf(" canon ");if(aip->ai_flags & AI_NUMERICHOST)printf(" numhost ");} }int main() {struct addrinfo *ailist,*aip;struct addrinfo hint;struct sockaddr_in *sinp;const char *addr;int err;char abuf[INET_ADDRSTRLEN];hint.ai_flags = AI_CANONNAME;hint.ai_family = 0;hint.ai_socktype = 0;hint.ai_protocol = 0;hint.ai_addrlen = 0;hint.ai_canonname = NULL;hint.ai_addr = NULL;hint.ai_next = NULL;if(getaddrinfo("localhost",NULL,&hint,&ailist) != 0){printf("getaddrinfo error: %s",gai_strerror(err));exit(-1);}for(aip = ailist;aip != NULL;aip = aip->ai_next){print_flags(aip);print_family(aip);print_type(aip);print_protocol(aip);printf("\n\thost %s",aip->ai_canonname ?aip->ai_canonname : "-");if(aip->ai_family == AF_INET){sinp = (struct sockaddr_in *)aip->ai_addr;addr = inet_ntop(AF_INET,&sinp->sin_addr,abuf,INET_ADDRSTRLEN);printf(" address %s ",addr?addr:"unknown");printf(" port %d ",ntohs(sinp->sin_port));}printf("\n");}exit(0); }下面是程序運行結果:
套接字與地址綁定
bind 函數
可以使用 bind 函數,將地址綁定到一個套接字。(man bind)
NAMEbind - bind a name to a socketSYNOPSIS#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
getsockname 函數和getpeername 函數
可以調用 getsockname 函數來得到綁定到套接字上的地址。(man getsockname):
NAMEgetsockname - get socket nameSYNOPSIS#include <sys/socket.h>int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);DESCRIPTIONgetsockname() returns the current address to which the socket sockfd is bound, in the buffer pointed to byaddr. The addrlen argument should be initialized to indicate the amount of space (in bytes) pointed to byaddr. On return it contains the actual size of the socket address.The returned address is truncated if the buffer provided is too small; in this case, addrlen will return avalue greater than was supplied to the call.RETURN VALUEOn success, zero is returned. On error, -1 is returned, and errno is set appropriately.
如果套接字已經和對等方連接,可以調用getpeername函數得到對方的地址。(man getpeername)。和 getsockname 用法相同:
NAMEgetpeername - get name of connected peer socketSYNOPSIS#include <sys/socket.h>int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);DESCRIPTIONgetpeername() returns the address of the peer connected to the socket sockfd, in the buffer pointed to byaddr. The addrlen argument should be initialized to indicate the amount of space pointed to by addr. Onreturn it contains the actual size of the name returned (in bytes). The name is truncated if the buffer pro‐vided is too small.The returned address is truncated if the buffer provided is too small; in this case, addrlen will return avalue greater than was supplied to the call.
3. 建立連接
connect 函數
使用 man connect 查看具體使用方法:
NAMEconnect - initiate a connection on a socketSYNOPSIS#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
下面代碼顯示了一種如何處理瞬時 connect 錯誤的方法,這在一個負載很重的服務器上很有可能發生。
#include "apue.h" #include <sys/socket.h>#define MAXSLEEP 128int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen) {int numsec;/** Try to connect with exponential backoff.*/for (numsec = 1; numsec <= MAXSLEEP; numsec <<= 1) {if (connect(sockfd, addr, alen) == 0) {/** Connection accepted.*/return(0);}/** Delay before trying again.*/if (numsec <= MAXSLEEP/2)sleep(numsec);}return(-1); }示例代碼:
#include "apue.h" #include <sys/socket.h>#define MAXSLEEP 128int connect_retry(int domain, int type, int protocol, const struct sockaddr *addr, socklen_t alen) {int numsec, fd;/** Try to connect with exponential backoff.*/for (numsec = 1; numsec <= MAXSLEEP; numsec <<= 1) {if ((fd = socket(domain, type, protocol)) < 0)return(-1);if (connect(fd, addr, alen) == 0) {/** Connection accepted.*/return(fd);}close(fd);/** Delay before trying again.*/if (numsec <= MAXSLEEP/2)sleep(numsec);}return(-1); }
listen 函數
服務器調用listen函數來宣告它愿意接受連接請求
#include <sys/socket.h> int listen(int sockfd,int backlog);
參數backlog提供了一個提示,提示系統改進程所要入隊的未完成連接請求數量。一旦隊列滿了,系統就會拒絕多余的連接請求。
一旦服務器調用了listen,所用的套接字就恩能夠接受連接請求。使用accept函數獲得連接請求并建立連接。
#include <sys/socket.h> int accept(int fd,struct sockaddr *restrict addr,socklen_t *restrict len);函數返回的是一個連接到調用connect的客戶端的套接字描述符
服務器初始化套接字:
#include "apue.h" #include <errno.h> #include <sys/socket.h>int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen) {int fd;int err = 0;if ((fd = socket(addr->sa_family, type, 0)) < 0)return(-1);if (bind(fd, addr, alen) < 0)goto errout;if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {if (listen(fd, qlen) < 0)goto errout;}return(fd);errout:err = errno;close(fd);errno = err;return(-1); }
4. 數據傳輸
發送數據的三個函數(man send)和?接收收據的三個函數(man recv)
NAMEsend, sendto, sendmsg - send a message on a socketSYNOPSIS#include <sys/types.h>#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);--------------------------------------------------------------------------------------------------------- NAMErecv, recvfrom, recvmsg - receive a message from a socketSYNOPSIS#include <sys/types.h>#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
發送數據的函數
send 函數
使用 man send 查看具體使用方法。最簡單的是 send 函數,它和 write 很像,但是可以指定標志來改變處理傳輸數據的方式。
使用 send 函數時,套接字必須已經連接,例如 使用 TCP 協議 的發送數據。參數 buf 和 len 與 write 函數中含義一致。
#include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags);下面總結了 send 函數中套接字 flags 可能的取值
sendto 函數
函數sendto和send很類似,區別在于sendto可以在無連接的套接字上指定一個目標地址。例如 使用 UDP 協議的發送數據。
#include <sys/socket.h> ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
sendmsg 函數
#include <sys/socket.h> ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);POSIX.1定義了msghdr結構,它至少有以下成員:
struct msghdr {void *msg_name; /* optional address */socklen_t msg_namelen; /* size of address */struct iovec *msg_iov; /* scatter/gather array */size_t msg_iovlen; /* # elements in msg_iov */void *msg_control; /* ancillary data, see below */size_t msg_controllen; /* ancillary data buffer len */int msg_flags; /* flags on received message */ };
接收數據的函數
recv 函數
函數recv和read相似,但是recv可以指定標志來控制如何接收數據
#include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
recvfrom 函數
#include <sys/socket.h> ssize_t recvfrom(int sockfd,void *restrict buf,size_t len,int flagsstruct sockaddr *restrict addr,socklen_t *restrict addrlen);
recvmsg 函數
#include <sys/socket.h> ssize_t recvmsg(int sockfd,struct msghdr *msg,int flags);
客戶機/服務器模式
在TCP/IP網絡應用中,通信的兩個進程間相互作用的主要模式是客戶機/服務器模式*(client/server),即客戶像服務其提出請求,服務器接受到請求后,提供相應的服務。
服務器:
(1)首先服務器方要先啟動,打開一個通信通道并告知本機,它愿意在某一地址和端口上接收客戶請求
(2)等待客戶請求到達該端口
(3)接收服務請求,處理該客戶請求,服務完成后,關閉此新進程與客戶的通信鏈路,并終止
(4)返回第二步,等待另一個客戶請求
(5)關閉服務器
客戶方:
(1)打開一個通信通道,并連接到服務器所在的主機特定的端口
(2)向服務器發送請求,等待并接收應答,繼續提出請求
(3)請求結束后關閉通信信道并終止
基于TCP(面向連接)的socket編程
服務器端先初始化Socket,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求并處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束。
示例程序
程序顯示一個客戶端命令,該命令用于與服務器通信以獲取系統命令 uptime 的輸出,該服務稱為“remote uptime”(簡稱 ruptime)
客戶端:
#include "apue.h" #include <netdb.h> #include <errno.h> #include <sys/socket.h>#define BUFLEN 128extern int connect_retry(int, int, int, const struct sockaddr *, socklen_t);void print_uptime(int sockfd) {int n;char buf[BUFLEN];while ((n = recv(sockfd, buf, BUFLEN, 0)) > 0)write(STDOUT_FILENO, buf, n);if (n < 0)err_sys("recv error"); }int main(int argc, char *argv[]) {struct addrinfo *ailist, *aip;struct addrinfo hint;int sockfd, err;if (argc != 2)err_quit("usage: ruptime hostname");memset(&hint, 0, sizeof(hint));hint.ai_socktype = SOCK_STREAM;hint.ai_canonname = NULL;hint.ai_addr = NULL;hint.ai_next = NULL;if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)err_quit("getaddrinfo error: %s", gai_strerror(err));for (aip = ailist; aip != NULL; aip = aip->ai_next) {if ((sockfd = connect_retry(aip->ai_family, SOCK_STREAM, 0, aip->ai_addr, aip->ai_addrlen)) < 0) {err = errno;} else {print_uptime(sockfd);exit(0);}}err_exit(err, "can't connect to %s", argv[1]); }
服務端:
#include "apue.h" #include <netdb.h> #include <errno.h> #include <syslog.h> #include <sys/socket.h>#define BUFLEN 128 #define QLEN 10#ifndef HOST_NAME_MAX #define HOST_NAME_MAX 256 #endifextern int initserver(int, const struct sockaddr *, socklen_t, int);void serve(int sockfd) {int clfd;FILE *fp;char buf[BUFLEN];set_cloexec(sockfd);for (;;) {if ((clfd = accept(sockfd, NULL, NULL)) < 0) {syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));exit(1);}set_cloexec(clfd);if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {sprintf(buf, "error: %s\n", strerror(errno));send(clfd, buf, strlen(buf), 0);} else {while (fgets(buf, BUFLEN, fp) != NULL)send(clfd, buf, strlen(buf), 0);pclose(fp);}close(clfd);} }int main(int argc, char *argv[]) {struct addrinfo *ailist, *aip;struct addrinfo hint;int sockfd, err, n;char *host;if (argc != 1) err_quit("usage: ruptimed");if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0) n = HOST_NAME_MAX; /* best guess */if ((host = malloc(n)) == NULL) err_sys("malloc error");if (gethostname(host, n) < 0) err_sys("gethostname error");daemonize("ruptimed");memset(&hint, 0, sizeof(hint));hint.ai_flags = AI_CANONNAME;hint.ai_socktype = SOCK_STREAM;hint.ai_canonname = NULL;hint.ai_addr = NULL;hint.ai_next = NULL;if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));exit(1);}for (aip = ailist; aip != NULL; aip = aip->ai_next) {if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0) {serve(sockfd);exit(0);}}exit(1); }
另一個版本的服務器程序
#include "apue.h" #include <netdb.h> #include <errno.h> #include <syslog.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/wait.h>#define QLEN 10#ifndef HOST_NAME_MAX #define HOST_NAME_MAX 256 #endifextern int initserver(int, const struct sockaddr *, socklen_t, int);void serve(int sockfd) {int clfd, status;pid_t pid;set_cloexec(sockfd);for (;;) {if ((clfd = accept(sockfd, NULL, NULL)) < 0) {syslog(LOG_ERR, "ruptimed: accept error: %s",strerror(errno));exit(1);}if ((pid = fork()) < 0) {syslog(LOG_ERR, "ruptimed: fork error: %s",strerror(errno));exit(1);} else if (pid == 0) { /* child *//** The parent called daemonize ({Prog daemoninit}), so* STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO* are already open to /dev/null. Thus, the call to* close doesn't need to be protected by checks that* clfd isn't already equal to one of these values.*/if (dup2(clfd, STDOUT_FILENO) != STDOUT_FILENO ||dup2(clfd, STDERR_FILENO) != STDERR_FILENO) {syslog(LOG_ERR, "ruptimed: unexpected error");exit(1);}close(clfd);execl("/usr/bin/uptime", "uptime", (char *)0);syslog(LOG_ERR, "ruptimed: unexpected return from exec: %s",strerror(errno));} else { /* parent */close(clfd);waitpid(pid, &status, 0);}} }int main(int argc, char *argv[]) {struct addrinfo *ailist, *aip;struct addrinfo hint;int sockfd, err, n;char *host;if (argc != 1)err_quit("usage: ruptimed");if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0)n = HOST_NAME_MAX; /* best guess */if ((host = malloc(n)) == NULL)err_sys("malloc error");if (gethostname(host, n) < 0)err_sys("gethostname error");daemonize("ruptimed");memset(&hint, 0, sizeof(hint));hint.ai_flags = AI_CANONNAME;hint.ai_socktype = SOCK_STREAM;hint.ai_canonname = NULL;hint.ai_addr = NULL;hint.ai_next = NULL;if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",gai_strerror(err));exit(1);}for (aip = ailist; aip != NULL; aip = aip->ai_next) {if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,aip->ai_addrlen, QLEN)) >= 0) {serve(sockfd);exit(0);}}exit(1); }
基于UDP(面向無連接)的socket編程
服務器先創建socket,將socket綁定(bind)一個本地地址和端口上,等待數據傳輸(recvfrom)。這個時候如果有個客戶端創建socket,并且向服務器發送數據(sendto),服務器就建立了連接,實現了數據的通信,連接結束后關閉連接。
示例程序
客戶端:
下面程序采用數據報套接字接口的uptime客戶端命令版本
#include "apue.h" #include <netdb.h> #include <errno.h> #include <sys/socket.h>#define BUFLEN 128 #define TIMEOUT 20void sigalrm(int signo) { }void print_uptime(int sockfd, struct addrinfo *aip) {int n;char buf[BUFLEN];buf[0] = 0;if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)err_sys("sendto error");alarm(TIMEOUT);if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) {if (errno != EINTR)alarm(0);err_sys("recv error");}alarm(0);write(STDOUT_FILENO, buf, n); }int main(int argc, char *argv[]) {struct addrinfo *ailist, *aip;struct addrinfo hint;int sockfd, err;struct sigaction sa;if (argc != 2)err_quit("usage: ruptime hostname");sa.sa_handler = sigalrm;sa.sa_flags = 0;sigemptyset(&sa.sa_mask);if (sigaction(SIGALRM, &sa, NULL) < 0)err_sys("sigaction error");memset(&hint, 0, sizeof(hint));hint.ai_socktype = SOCK_DGRAM;hint.ai_canonname = NULL;hint.ai_addr = NULL;hint.ai_next = NULL;if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)err_quit("getaddrinfo error: %s", gai_strerror(err));for (aip = ailist; aip != NULL; aip = aip->ai_next) {if ((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) {err = errno;} else {print_uptime(sockfd, aip);exit(0);}}fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));exit(1); }
服務器程序:
#include "apue.h" #include <netdb.h> #include <errno.h> #include <syslog.h> #include <sys/socket.h>#define BUFLEN 128 #define MAXADDRLEN 256#ifndef HOST_NAME_MAX #define HOST_NAME_MAX 256 #endifextern int initserver(int, const struct sockaddr *, socklen_t, int);void serve(int sockfd) {int n;socklen_t alen;FILE *fp;char buf[BUFLEN];char abuf[MAXADDRLEN];struct sockaddr *addr = (struct sockaddr *)abuf;set_cloexec(sockfd);for (;;) {alen = MAXADDRLEN;if ((n = recvfrom(sockfd, buf, BUFLEN, 0, addr, &alen)) < 0) {syslog(LOG_ERR, "ruptimed: recvfrom error: %s",strerror(errno));exit(1);}if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {sprintf(buf, "error: %s\n", strerror(errno));sendto(sockfd, buf, strlen(buf), 0, addr, alen);} else {if (fgets(buf, BUFLEN, fp) != NULL)sendto(sockfd, buf, strlen(buf), 0, addr, alen);pclose(fp);}} }int main(int argc, char *argv[]) {struct addrinfo *ailist, *aip;struct addrinfo hint;int sockfd, err, n;char *host;if (argc != 1)err_quit("usage: ruptimed");if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0)n = HOST_NAME_MAX; /* best guess */if ((host = malloc(n)) == NULL)err_sys("malloc error");if (gethostname(host, n) < 0)err_sys("gethostname error");daemonize("ruptimed");memset(&hint, 0, sizeof(hint));hint.ai_flags = AI_CANONNAME;hint.ai_socktype = SOCK_DGRAM;hint.ai_canonname = NULL;hint.ai_addr = NULL;hint.ai_next = NULL;if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",gai_strerror(err));exit(1);}for (aip = ailist; aip != NULL; aip = aip->ai_next) {if ((sockfd = initserver(SOCK_DGRAM, aip->ai_addr,aip->ai_addrlen, 0)) >= 0) {serve(sockfd);exit(0);}}exit(1); }
5. 套接字選項
套接字機制提供了設置跟查詢套接字選項的接口。可以獲取或設置以下3種選項
1.通用選項,工作在所有套接字類型上
2.在套接字層次管理的選項,但是依賴于下層協議的支持
3.特定于某協議的選項,每個協議獨有的
可以使用setsockopt函數來設置套接字選項
#include <sys/socket.h> int setsockopt(int sockfd,int level,int option,const void *val,socklen_t len);level標識了選項應用的協議:
如果選項是通用的套接字層次選項,則level設置為SOL_SOCKET,否則,level設置成控制這個選項的協議編號。對于TCP選項,level是IPPROTO_TCP,對于IP,level是IPPROTO_IP。
下面總結了Single UNIX Specification中定義的通用套接字層次選項
可以使用getsockopt函數來查看選項的當前值
#include <sys/socket.h> int getsockopt(int sockfd,int level,int option,void *restrict val,socklen_t restrict lenp);采用地址復用初始化套接字端點:
#include "apue.h" #include <errno.h> #include <sys/socket.h>int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen) {int fd, err;int reuse = 1;if ((fd = socket(addr->sa_family, type, 0)) < 0)return(-1);if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse,sizeof(int)) < 0)goto errout;if (bind(fd, addr, alen) < 0)goto errout;if (type == SOCK_STREAM || type == SOCK_SEQPACKET)if (listen(fd, qlen) < 0)goto errout;return(fd);errout:err = errno;close(fd);errno = err;return(-1); }
6. 帶外數據
帶外數據是一些通信協議所支持的可選特征,允許更加高級的數據比普通數據優先傳輸。
TCP將帶外數據稱為緊急數據。TCP僅支持一個字節的緊急數據,但是允許緊急數據在普通數據傳遞機制數據流之外傳輸。
為了產生緊急數據,在三個send函數中任何一個指定標志MSG_OOB。如果帶MSG_OOB標志傳輸字節超過一個時,最后一個字節被作為緊急數據字節。
如果安排發生套接字信號,當接收到緊急數據時,那么發送信號SIGURG信號。可以通過調用以下函數安排進程接收套接字的信號:
fcntl(sockfd,F_SETTOWN,pid);F_GETOWN命令可以用來獲取當前套接字所有權
owner=fcntl(sockfd,F_GETOWN,0);為幫助判斷是否已經達到緊急標記,可以使用函數sockatmark
#include <sys/socket.h> int sockatmark(int sockfd);當下一個要讀取的字節在緊急標志處時,sockatmark返回1。
7. 非阻塞和異步I/O
在基于套接字異步I/O中,當能夠從套接字中讀取數據,或者套接字寫隊列中的空間變得可用時,可以安排發送信號SIGIO。通過兩個步驟來使用異步I/O:
1) 建立套接字擁有者關系,信號可以被傳送到合適的進程。
2) 通知套接字當I/O操作不會阻塞時發信號告知。
可以使用三種方式來完成第一個步驟:
A、 在fcntl使用F_SETOWN命令
B、 在ioctl中作用FIOSETOWN命令
C、 在ioctl中使用SIOCSPGRP命令。
???? 要完成第二個步驟,有兩個選擇:
A、 在fcntl中使用F_SETFL命令并且啟用文件標志O_ASYNC。
B、 在ioctl中使用FIOASYNC
示例
TCP 示例 客戶端 和服務端程序:
服務端代碼:
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <ctype.h> #include <unistd.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h>#define SERV_PORT 8000int main(void) {int sfd, cfd;struct sockaddr_in serv_addr, clin_addr; //IP + 端口號socklen_t clin_addr_len;char buf[4096];int len, i;//AF_INET:ipv4協議 SOCK_STREAM:流式協議 0:流式協議里默認協議TCPsfd = socket(AF_INET, SOCK_STREAM, 0); /* 構造本地進程綁定的地址信息 */bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET; // IPv4地址類型serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //綁定本機所有IPserv_addr.sin_port = htons(SERV_PORT); //綁定端口號,轉換為網絡序/* 本地進程綁定,地址信息+socket文件描述符 */bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));/* 設置socket同時能處理多少客戶端鏈接 */listen(sfd, 20);clin_addr_len = sizeof(clin_addr);cfd = accept(sfd, (struct sockaddr *)&clin_addr, &clin_addr_len);while (1) {len = read(cfd, buf, sizeof(buf));//接收客戶端發送過來的數據//模擬處理數據,小寫轉大寫for (i = 0; i < len; i++) buf[i] = toupper(buf[i]);write(cfd, buf, len);//把處理后的數據發回客戶端}close(cfd); //關閉和客戶端鏈接的socketclose(sfd); //關閉accept監聽用的socketreturn 0; }
客戶端代碼:
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <ctype.h> #include <unistd.h> #include <strings.h> #include <string.h> #include <arpa/inet.h> #include <netinet/in.h>#define SERV_PORT 8000 #define SERV_IP "127.0.0.1"int main(int argc, char *argv[]) {struct sockaddr_in serv_addr;int cfd, len;char buf[1024];cfd = socket(AF_INET, SOCK_STREAM, 0);bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); //serv_addr.sin_addr.s_addr = SERV_IP;serv_addr.sin_port = htons(SERV_PORT);connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));while (fgets(buf, sizeof(buf), stdin)) {write(cfd, buf, strlen(buf));len = read(cfd, buf, sizeof(buf));write(STDOUT_FILENO, buf, len);}close(cfd);return 0; }
總結
以上是生活随笔為你收集整理的网络 IPC 套接字socket的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在 windows 下使用 Xming+
- 下一篇: SpringBoot 自带工具类~Ref