网络 IPC 套接字socket
APUE書中所有實(shí)例源碼下載地址:http://www.apuebook.com
apue學(xué)習(xí)筆記(第十六章 網(wǎng)絡(luò)IPC:套接字):https://www.cnblogs.com/runnyu/p/4648678.html
一起學(xué) Unix 環(huán)境高級編程 (APUE) 之 網(wǎng)絡(luò) IPC:套接字:http://www.cnblogs.com/0xcafebabe/p/4478824.html
linux下網(wǎng)絡(luò)編程:http://blog.csdn.net/freeking101/article/details/71449111
根據(jù) APUE (第十六章 網(wǎng)絡(luò)IPC:套接字) 整理,如有疑問,直接看 APUE。。。
1. 套接字描述符
socket 函數(shù)
使用 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. 地址
字節(jié)序
IP地址+端口?可以唯一確定一個(gè)socket進(jìn)程。運(yùn)行在同一計(jì)算機(jī)上進(jìn)程之間相互通信時(shí)。一般不用考慮字節(jié)的順序(字節(jié)序分為大端序和小端序。小端序:低地址存放數(shù)據(jù)低位,高地址存放數(shù)據(jù)高位,可以記憶為:小弟弟(低低),小高高。雖說有點(diǎn)黃但好記,大端序和小端序存儲相反)。字節(jié)序是一個(gè)處理器架構(gòu)特性,用于指示像整數(shù)這樣的大數(shù)據(jù)類型的內(nèi)部字節(jié)順序。不同架構(gòu)的處理器可能是大端也可能是小端,有些處理器可以配置成大端或者小端。字節(jié)序不一樣,數(shù)據(jù)就不能正常處理。
大端格式:低地址存放高位數(shù)據(jù),高地址存放低位數(shù)據(jù)。 小端格式:低地址存放低位數(shù)據(jù),高地址存放高位數(shù)據(jù)。
如圖所示,假設(shè)要存放的數(shù)據(jù)是 0x30313233,那么 33 是低位,30 是高位,在大端存儲格式中,30 存放在低位,33 存放在高位;而在小端存儲格式中,33 存放在低位,30 存放在高位。這個(gè)東西有什么作用呢?它其實(shí)就是我們使用的網(wǎng)絡(luò)設(shè)備(計(jì)算機(jī)、平板電腦、智能手機(jī)等等)在內(nèi)存當(dāng)中存儲數(shù)據(jù)的格式。
處理器字節(jié)序和網(wǎng)絡(luò)序轉(zhuǎn)換函數(shù)
既然通訊雙方的設(shè)備存儲數(shù)據(jù)的格式不同,那么一端發(fā)送過去的數(shù)據(jù),另一端是無法正確解析的,這該怎么辦?沒關(guān)系,其實(shí)系統(tǒng)準(zhǔn)備了一組函數(shù)可以實(shí)現(xiàn)字節(jié)序轉(zhuǎn)換,我們可以像使用公式一樣使用它們。
地址格式
二進(jìn)制地址和點(diǎn)分十進(jìn)制字符串地址轉(zhuǎn)換
地址內(nèi)部結(jié)構(gòu)信息查詢
相關(guān)函數(shù)(可以使用 man 命令查看函數(shù)的詳細(xì)使用幫助):
get network host entry (得到給定計(jì)算機(jī)的主機(jī)信息)gethostbyname, gethostbyaddr, sethostent, gethostent, endhostent, h_errno, herror, hstrerror, gethostbyaddr_r,gethostbyname2, gethostbyname2_r, gethostbyname_r, gethostent_r get network entry (得到網(wǎng)絡(luò)和網(wǎng)絡(luò)號相關(guān)信息)getnetent, getnetbyname, getnetbyaddr, setnetent, endnetentget protocol entry (得到協(xié)議和協(xié)議號相關(guān)信息)getprotoent, getprotobyname, getprotobynumber, setprotoent, endprotoent get service entry (得到服務(wù)相關(guān)信息。服務(wù)是由地址的端口號部分表示的)getservent, getservbyname, getservbyport, setservent, endserventnetwork address and service translation (網(wǎng)絡(luò)地址和服務(wù)映射)getaddrinfo, freeaddrinfo, gai_strerror
示例 使用getaddrinfo 打印主機(jī)和服務(wù)信息
使用到的數(shù)據(jù)結(jié)構(gòu)
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()取得輸入主機(jī)和服務(wù)的 addrinfo解構(gòu),按照hint的過濾作用保存在ailist中,依次打印flag,type等,在通過定義struct sockaddr_in snip來保存AF_INET family 這一簇地址格式的 aip->ai_addr;使用inet_ntop()函數(shù)把得到的地址信息解析出來,放到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); }下面是程序運(yùn)行結(jié)果:
套接字與地址綁定
bind 函數(shù)
可以使用 bind 函數(shù),將地址綁定到一個(gè)套接字。(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 函數(shù)和getpeername 函數(shù)
可以調(diào)用 getsockname 函數(shù)來得到綁定到套接字上的地址。(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.
如果套接字已經(jīng)和對等方連接,可以調(diào)用getpeername函數(shù)得到對方的地址。(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 函數(shù)
使用 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);
下面代碼顯示了一種如何處理瞬時(shí) connect 錯(cuò)誤的方法,這在一個(gè)負(fù)載很重的服務(wù)器上很有可能發(fā)生。
#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 函數(shù)
服務(wù)器調(diào)用listen函數(shù)來宣告它愿意接受連接請求
#include <sys/socket.h> int listen(int sockfd,int backlog);
參數(shù)backlog提供了一個(gè)提示,提示系統(tǒng)改進(jìn)程所要入隊(duì)的未完成連接請求數(shù)量。一旦隊(duì)列滿了,系統(tǒng)就會拒絕多余的連接請求。
一旦服務(wù)器調(diào)用了listen,所用的套接字就恩能夠接受連接請求。使用accept函數(shù)獲得連接請求并建立連接。
#include <sys/socket.h> int accept(int fd,struct sockaddr *restrict addr,socklen_t *restrict len);函數(shù)返回的是一個(gè)連接到調(diào)用connect的客戶端的套接字描述符
服務(wù)器初始化套接字:
#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. 數(shù)據(jù)傳輸
發(fā)送數(shù)據(jù)的三個(gè)函數(shù)(man send)和?接收收據(jù)的三個(gè)函數(shù)(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);
發(fā)送數(shù)據(jù)的函數(shù)
send 函數(shù)
使用 man send 查看具體使用方法。最簡單的是 send 函數(shù),它和 write 很像,但是可以指定標(biāo)志來改變處理傳輸數(shù)據(jù)的方式。
使用 send 函數(shù)時(shí),套接字必須已經(jīng)連接,例如 使用 TCP 協(xié)議 的發(fā)送數(shù)據(jù)。參數(shù) buf 和 len 與 write 函數(shù)中含義一致。
#include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags);下面總結(jié)了 send 函數(shù)中套接字 flags 可能的取值
sendto 函數(shù)
函數(shù)sendto和send很類似,區(qū)別在于sendto可以在無連接的套接字上指定一個(gè)目標(biāo)地址。例如 使用 UDP 協(xié)議的發(fā)送數(shù)據(jù)。
#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 函數(shù)
#include <sys/socket.h> ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);POSIX.1定義了msghdr結(jié)構(gòu),它至少有以下成員:
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 */ };
接收數(shù)據(jù)的函數(shù)
recv 函數(shù)
函數(shù)recv和read相似,但是recv可以指定標(biāo)志來控制如何接收數(shù)據(jù)
#include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
recvfrom 函數(shù)
#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 函數(shù)
#include <sys/socket.h> ssize_t recvmsg(int sockfd,struct msghdr *msg,int flags);
客戶機(jī)/服務(wù)器模式
在TCP/IP網(wǎng)絡(luò)應(yīng)用中,通信的兩個(gè)進(jìn)程間相互作用的主要模式是客戶機(jī)/服務(wù)器模式*(client/server),即客戶像服務(wù)其提出請求,服務(wù)器接受到請求后,提供相應(yīng)的服務(wù)。
服務(wù)器:
(1)首先服務(wù)器方要先啟動(dòng),打開一個(gè)通信通道并告知本機(jī),它愿意在某一地址和端口上接收客戶請求
(2)等待客戶請求到達(dá)該端口
(3)接收服務(wù)請求,處理該客戶請求,服務(wù)完成后,關(guān)閉此新進(jìn)程與客戶的通信鏈路,并終止
(4)返回第二步,等待另一個(gè)客戶請求
(5)關(guān)閉服務(wù)器
客戶方:
(1)打開一個(gè)通信通道,并連接到服務(wù)器所在的主機(jī)特定的端口
(2)向服務(wù)器發(fā)送請求,等待并接收應(yīng)答,繼續(xù)提出請求
(3)請求結(jié)束后關(guān)閉通信信道并終止
基于TCP(面向連接)的socket編程
服務(wù)器端先初始化Socket,然后與端口綁定(bind),對端口進(jìn)行監(jiān)聽(listen),調(diào)用accept阻塞,等待客戶端連接。在這時(shí)如果有個(gè)客戶端初始化一個(gè)Socket,然后連接服務(wù)器(connect),如果連接成功,這時(shí)客戶端與服務(wù)器端的連接就建立了。客戶端發(fā)送數(shù)據(jù)請求,服務(wù)器端接收請求并處理請求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端,客戶端讀取數(shù)據(jù),最后關(guān)閉連接,一次交互結(jié)束。
示例程序
程序顯示一個(gè)客戶端命令,該命令用于與服務(wù)器通信以獲取系統(tǒng)命令 uptime 的輸出,該服務(wù)稱為“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]); }
服務(wù)端:
#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); }
另一個(gè)版本的服務(wù)器程序
#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編程
服務(wù)器先創(chuàng)建socket,將socket綁定(bind)一個(gè)本地地址和端口上,等待數(shù)據(jù)傳輸(recvfrom)。這個(gè)時(shí)候如果有個(gè)客戶端創(chuàng)建socket,并且向服務(wù)器發(fā)送數(shù)據(jù)(sendto),服務(wù)器就建立了連接,實(shí)現(xiàn)了數(shù)據(jù)的通信,連接結(jié)束后關(guān)閉連接。
示例程序
客戶端:
下面程序采用數(shù)據(jù)報(bào)套接字接口的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); }
服務(wù)器程序:
#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. 套接字選項(xiàng)
套接字機(jī)制提供了設(shè)置跟查詢套接字選項(xiàng)的接口。可以獲取或設(shè)置以下3種選項(xiàng)
1.通用選項(xiàng),工作在所有套接字類型上
2.在套接字層次管理的選項(xiàng),但是依賴于下層協(xié)議的支持
3.特定于某協(xié)議的選項(xiàng),每個(gè)協(xié)議獨(dú)有的
可以使用setsockopt函數(shù)來設(shè)置套接字選項(xiàng)
#include <sys/socket.h> int setsockopt(int sockfd,int level,int option,const void *val,socklen_t len);level標(biāo)識了選項(xiàng)應(yīng)用的協(xié)議:
如果選項(xiàng)是通用的套接字層次選項(xiàng),則level設(shè)置為SOL_SOCKET,否則,level設(shè)置成控制這個(gè)選項(xiàng)的協(xié)議編號。對于TCP選項(xiàng),level是IPPROTO_TCP,對于IP,level是IPPROTO_IP。
下面總結(jié)了Single UNIX Specification中定義的通用套接字層次選項(xiàng)
可以使用getsockopt函數(shù)來查看選項(xiàng)的當(dāng)前值
#include <sys/socket.h> int getsockopt(int sockfd,int level,int option,void *restrict val,socklen_t restrict lenp);采用地址復(fù)用初始化套接字端點(diǎn):
#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. 帶外數(shù)據(jù)
帶外數(shù)據(jù)是一些通信協(xié)議所支持的可選特征,允許更加高級的數(shù)據(jù)比普通數(shù)據(jù)優(yōu)先傳輸。
TCP將帶外數(shù)據(jù)稱為緊急數(shù)據(jù)。TCP僅支持一個(gè)字節(jié)的緊急數(shù)據(jù),但是允許緊急數(shù)據(jù)在普通數(shù)據(jù)傳遞機(jī)制數(shù)據(jù)流之外傳輸。
為了產(chǎn)生緊急數(shù)據(jù),在三個(gè)send函數(shù)中任何一個(gè)指定標(biāo)志MSG_OOB。如果帶MSG_OOB標(biāo)志傳輸字節(jié)超過一個(gè)時(shí),最后一個(gè)字節(jié)被作為緊急數(shù)據(jù)字節(jié)。
如果安排發(fā)生套接字信號,當(dāng)接收到緊急數(shù)據(jù)時(shí),那么發(fā)送信號SIGURG信號。可以通過調(diào)用以下函數(shù)安排進(jìn)程接收套接字的信號:
fcntl(sockfd,F_SETTOWN,pid);F_GETOWN命令可以用來獲取當(dāng)前套接字所有權(quán)
owner=fcntl(sockfd,F_GETOWN,0);為幫助判斷是否已經(jīng)達(dá)到緊急標(biāo)記,可以使用函數(shù)sockatmark
#include <sys/socket.h> int sockatmark(int sockfd);當(dāng)下一個(gè)要讀取的字節(jié)在緊急標(biāo)志處時(shí),sockatmark返回1。
7. 非阻塞和異步I/O
在基于套接字異步I/O中,當(dāng)能夠從套接字中讀取數(shù)據(jù),或者套接字寫隊(duì)列中的空間變得可用時(shí),可以安排發(fā)送信號SIGIO。通過兩個(gè)步驟來使用異步I/O:
1) 建立套接字擁有者關(guān)系,信號可以被傳送到合適的進(jìn)程。
2) 通知套接字當(dāng)I/O操作不會阻塞時(shí)發(fā)信號告知。
可以使用三種方式來完成第一個(gè)步驟:
A、 在fcntl使用F_SETOWN命令
B、 在ioctl中作用FIOSETOWN命令
C、 在ioctl中使用SIOCSPGRP命令。
???? 要完成第二個(gè)步驟,有兩個(gè)選擇:
A、 在fcntl中使用F_SETFL命令并且啟用文件標(biāo)志O_ASYNC。
B、 在ioctl中使用FIOASYNC
示例
TCP 示例 客戶端 和服務(wù)端程序:
服務(wù)端代碼:
#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協(xié)議 SOCK_STREAM:流式協(xié)議 0:流式協(xié)議里默認(rèn)協(xié)議TCPsfd = socket(AF_INET, SOCK_STREAM, 0); /* 構(gòu)造本地進(jìn)程綁定的地址信息 */bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET; // IPv4地址類型serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //綁定本機(jī)所有IPserv_addr.sin_port = htons(SERV_PORT); //綁定端口號,轉(zhuǎn)換為網(wǎng)絡(luò)序/* 本地進(jìn)程綁定,地址信息+socket文件描述符 */bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));/* 設(shè)置socket同時(shí)能處理多少客戶端鏈接 */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));//接收客戶端發(fā)送過來的數(shù)據(jù)//模擬處理數(shù)據(jù),小寫轉(zhuǎn)大寫for (i = 0; i < len; i++) buf[i] = toupper(buf[i]);write(cfd, buf, len);//把處理后的數(shù)據(jù)發(fā)回客戶端}close(cfd); //關(guān)閉和客戶端鏈接的socketclose(sfd); //關(guān)閉accept監(jiān)聽用的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; }
總結(jié)
以上是生活随笔為你收集整理的网络 IPC 套接字socket的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在 windows 下使用 Xming+
- 下一篇: Scrapy-redis 源码分析 及