通过网络套接字可以使得不同计算机上运行的进程相互通信。
1、创建套接字
#include <sys/socket.h>
Int socket( int domain, int type, int protocol);
注意:AF_LOCAL域是AF_UNIX的别名,AF_UNSPEC域可以代表任何域。
2、套接字通信是双向的,禁止套接字上的输入/输出
#include < sys/socket.h>
Int shutdown ( int sockfd, int how);
3、处理字节序和网络字节序之间的轮换:
#include< arpa/inet.h>
Uint32_t htonl (uint32_t hostint32);
Uint16_t htons(uint16_t hostint16);
Uint32_t ntohl (uint32_t netint32);
Uint16_t ntohs (uint16_t netint16);
4、地址格式:
Struct sockaddr{
Sa_family_t sa_family;
Char sa_data[];}
在Linux中,该结构定义如下:
Struct sockaddr{
Sa_family_t sa_family;
Char sa_data[14];};
而在FreeBSD中,该结构定义如下:
Struct sockaddr{
Unsigned char sa_len;
Sa_family_t sa_family;
Char sa_data[14];};
因特网地址定义在<netinet/in.h>中。在IPV4因特网域(AF_INET)中,套接字地址用如下结构sokaddr_in表示:
Struct in_addr{
In_addr_t s_addr;}
Struct sockaddr_in{
Sa_family_t sin_family;
In_port_t sin_port;
Struct in_addr sin_addr;
};
数据类型in_port_t 定义成uint16_t。数据类型in_addr_t 定义成uint32_t。这些整数类型在<sdint.h>中定义并指定了相应的位数。
与IPV4因特网域(AF_INET)相比较,IPV6因特网域(AF_INET6)套接字地址用如下结构sockaddr_in6表示:
Struct in6_addr{
Uint8_t s6_addr[16];};
Struct sockaddr_in6{
Sa_family_t sin6_family;
In_port_t sin6_port;
Uint32_t sin6_flowinfo;
Struct in6_addr sin6_addr;
Uint32_t sin6_scope_id;}
在Linux中,sockaddr_in 定义如下:
Struct sockaddr_in{
Sa_family_t sin_family;
In_port_t sin_port;
Struct in_addr sin_addr;
Unsigned char sin_zero[8];};
5、打印出能被人理解的地址格式函数
#include <arpa/inet.h>
const char * inet_ntop (int domain, const void * restrict addr, char * restrict str, socklen_t size);
Int inet_pton ( int domain, const char * restrict str, void * restrict addr);
6、找给定计算机的主机信息
#include<netdb.h>
Struct hostent * gethostent (void);
Void sethostent (int stayopen);
Void endhostent (void);
Struct hostent{
Char * h_name;
Char ** h_aliases;
Int h_addrtype;
Int h_length;
Char ** h_addr_list;};
7、从接口获得网络名字和网络号:
#include<netdb.h.
Struct netent * getnetbyaddr (uint32_t net, int type);
Struct netent * getnetbyname (const char * name);
Struct netent * getnetent (void);
Void setnetent (int stayopen);
Void endnetent (void);
Struct netent{
Char * n_name;
Char ** n_aliases;
Int n_addrtype;
Uint32_t n_net;};
8、将协议名字和协议号采用以下函数映射
#include <netdb.h>
Struct protoent * getprotobyname (const char * name);
Struct protoent * getprotobynumber (int proto);
Struct protoent * getprotoent (void);
Void setprotoent (int stayopen);
Void endprotoent (void);
Struct protoent{
Char * p_name;
Char ** p_aliases;
Int p_proto;};
9、从一个服务名映射到一个端口号,服务名
#include<netdb.h>
Struct servent * getservbyname (const char * name, const char * proto);
Struct servent * getservbyport (int port, const char * proto);
Struct servent * getservent( (void);
Void setervent (int stayopen);
Void endservent (void);
Struct servent{
Char * s_name;
Char ** s_aliases;
Int s_port;
Char * s_proto;};
10、从一个主机名字和服务名字映射到一个地址
#include <sys/socket.h>
#include <netdb.h>
Int getaddrinfo (const char * restrict host, const char * restrict service, const struct addrinfo * restrict hint, struct addrinfo ** restrict res);
Void freeaddrinfo (struct addrinfo * ai);
Struct addrinfo{
Int ai_flags;
int ai_family;
Int ai_socktype;
Int ai_protocol;
Socklen_t ai_addrlen;
Struct sockaddr * ai_addr;
Char * ai_canonname;
Struct addrinfo * ai_next;};
11、gai_strerror将返回的错误码转换成错误消息
#include<netdb.h>
Const char * gai_strerror (int error);
12、将地址转换成主机或者服务名
#include<sys/socket.h>
#include <netdb.h>
Int getnameinfo (const struct sockaddr * restrict addr, socklen_t alen, char * restrict host,socklen_t hostlen, char * restrict service, socklen_t servlen, unsigned int flags);
写一个程序获取本机的网络信息,程序如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <arpa/inet.h>
5 #include <netdb.h>
6 #include <sys/socket.h>
7
8 void print_family(struct addrinfo *aip)
9 {
10 printf("family ");
11 switch(aip->ai_family)
12 {
13 case AF_INET:
14 printf("inet ");
15 break;
16 case AF_INET6:
17 printf("inet6 ");
18 break;
19 case AF_UNIX:
20 printf("unix ");
21 break;
22 case AF_UNSPEC:
23 printf("unspecified ");
24 break;
25 default:
26 printf("unkown ");
27 }
28 }
29
30 void print_type(struct addrinfo *aip)
31 {
32 printf("type ");
33 switch(aip->ai_socktype)
34 {
35 case SOCK_STREAM:
36 printf("stream ");
37 break;
38 case SOCK_DGRAM:
39 printf("datagram");
40 break;
41 case SOCK_SEQPACKET:
42 printf("seqpacket ");
43 break;
44 case SOCK_RAW:
45 printf("raw ");
46 break;
47 default:
48 printf("unknown (%d)",aip->ai_socktype);
49 }
50 }
51
52 void print_protocol(struct addrinfo *aip)
53 {
54 printf(" protocol ");
55 switch(aip->ai_protocol)
56 {
57 case 0:
58 printf("default ");
59 break;
60 case IPPROTO_TCP:
61 printf("TCP ");
62 break;
63 case IPPROTO_UDP:
64 printf("UDP ");
65 break;
66 case IPPROTO_RAW:
67 printf("raw ");
68 break;
69 default:
70 printf("unknown (%d)",aip->ai_protocol);
71 }
72 }
73
74 void print_flags(struct addrinfo *aip)
75 {
76 printf("flags");
77 if(aip->ai_flags == 0)
78 printf("0");
79 else
80 {
81 if(aip->ai_flags & AI_PASSIVE)
82 printf(" passive ");
83 if(aip->ai_flags & AI_CANONNAME)
84 printf(" canon ");
85 if(aip->ai_flags & AI_NUMERICHOST)
86 printf(" numhost ");
87 }
88 }
89
90 int main()
91 {
92 struct addrinfo *ailist,*aip;
93 struct addrinfo hint;
94 struct sockaddr_in *sinp;
95 const char *addr;
96 int err;
97 char abuf[INET_ADDRSTRLEN];
98 hint.ai_flags = AI_CANONNAME;
99 hint.ai_family = 0;
100 hint.ai_socktype = 0;
101 hint.ai_protocol = 0;
102 hint.ai_addrlen = 0;
103 hint.ai_canonname = NULL;
104 hint.ai_addr = NULL;
105 hint.ai_next = NULL;
106 if(getaddrinfo("localhost",NULL,&hint,&ailist) != 0)
107 {
108 printf("getaddrinfo error: %s",gai_strerror(err));
109 exit(-1);
110 }
111 for(aip = ailist;aip != NULL;aip = aip->ai_next)
112 {
113 print_flags(aip);
114 print_family(aip);
115 print_type(aip);
116 print_protocol(aip);
117 printf("\n\thost %s",aip->ai_canonname ?aip->ai_canonname : "-");
118 if(aip->ai_family == AF_INET)
119 {
120 sinp = (struct sockaddr_in *)aip->ai_addr;
121 addr = inet_ntop(AF_INET,&sinp->sin_addr,abuf,INET_ADDRSTRLEN);
122 printf(" address %s ",addr?addr:"unknown");
123 printf(" port %d ",ntohs(sinp->sin_port));
124 }
125 printf("\n");
126 }
127 exit(0);
128 }
程序执行结果如下:
13、将套接字与地址绑定
#include <sys/socket.h>
Int bind (int sockfd, const struct sockaddr * addr, socklen_t len );
对于使用的地址有一些限制:
A、 在进程所运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址。
B、 地址必须和创建套接字时的地址族支持的格式相匹配。
C、 端口号必须不小于1024,除非该进程具有相应的特权(即为超级用户)。
D、一般只有套接字端点能够与地址绑定,尽管有些协议允许多重绑定。
14、获取绑定到一个套接字的地址:
#include <sys/socket.h>
Int getsockname ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict alenp);
注意:在调用getsockname之前,设置alenp为一个指向整数的指针,该整数指定缓冲区sockaddr的大小。返回时,该整数会被设置成返回地址的大小。如果该地址和提供的缓冲区长度不匹配,则将其截断而不报错。如果当前没有绑定到该套接字的地址,其结果没有定义。
15、获得对方地址:
#include <sys/socket.h>
Int getpeername ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict alenp);
注意:如果套接字已经和对方连接,调用getpeername来找到对方的地址。除了还会返回对方的地址之外,函数getpeername和getsockname一样。
16、建立连接
#include <sys/socket.h>
Int connect ( int sockfd, const struct sockaddr * addr, socklen_t len);
17、listen函数
#include <sys/socket.h>
Int listen ( int sockfd,int backlog);
18、accept函数
#include <sys/socket.h>
Int accept ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict len);
19、send、sendto以及sendmsg信息发送函数
#include <sys/socket.h>
Ssize_t send ( int sockfd, const void * buf, size_t nbytes, int flags);
Ssize_t sendto ( int sockfd, const void * buf, size_t nbytes, int flags, const struct sockaddr * destaddr, socklen_t destlen);
Ssize_t sendmsg ( int sockfd, const struct msghdr * msg, int flags);
Struct msghdr{
Void * msg_name;
Socklen_t msg_namelen;
Struct iovec * msg_iov;
Int msg_iovlen;
Void * msg_control;
Socklen_t msg_controllen;
Int msg_flags;
};
19、recv、recvfrom 与recvmsg接收数据函数
#include <sys/socket.h>
Ssize_t recv ( int sockfd, void * buf, size_t nbytes, int flags);
Ssize_t recvfrom ( int sockfd, void * restrict buf, size_t len, int flags, struct sockaddr * restrict addr, socklen_t * restrict addrlen);
Ssize_t recvmsg ( int sockfd, struct msghdr * msg, int flags);
写个程序练习网络IPC,客户端请求服务器获取当前时间,服务器调用uptime命令将时间发送给客户端。程序如下:
客户端程序:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <arpa/inet.h>
5 #include <netdb.h>
6 #include <sys/socket.h>
7 #include <errno.h>
8 #include <string.h>
9 #define MAXADDRLEN 256
10 #define BUFLEN 128
11 #define MAXSLEEP 128
12 #define HOST_NAME_MAX 256
13 //尝试重试的连接
14 int connect_retry(int sockfd,const struct sockaddr *addr,socklen_t alen)
15 {
16 int nsec;
17 for(nsec =1;nsec <= MAXSLEEP;nsec<<=1)
18 {
19 if(connect(sockfd,addr,alen) == 0)
20 return 0;
21 if(nsec <= MAXSLEEP/2)
22 sleep(nsec);
23 }
24 return -1;
25 }
26 void print_uptime(int sockfd)
27 {
28 int n;
29 char buf[BUFLEN];
30 while((n=recv(sockfd,buf,BUFLEN,0)) > 0)
31 write(STDOUT_FILENO,buf,n);
32 if(n<0)
33 {
34 perror("recv error");
35 exit(-1);
36 }
37 }
38 int main()
39 {
40 struct addrinfo *ailist,*aip;
41 struct addrinfo hint;
42 int sockfd;
43 int err,n;
44 char *host;
45 #ifdef _SC_HOST_NAME_MAX
46 n = sysconf(_SC_HOST_NAME_MAX);
47 if(n<0)
48 #endif
49 n = HOST_NAME_MAX;
50 host = (char*)malloc(n);
51 if(host == NULL)
52 {
53 perror("malloc() error");
54 exit(-1);
55 }
56 if(gethostname(host,n) < 0)
57 {
58 perror("gethostname() error");
59 exit(-1);
60 }
61 hint.ai_flags = 0;
62 hint.ai_family = 0;
63 hint.ai_socktype = SOCK_STREAM;
64 hint.ai_protocol = 0;
65 hint.ai_addrlen = 0;
66 hint.ai_canonname = NULL;
67 hint.ai_addr = NULL;
68 hint.ai_next = NULL;
69 if(getaddrinfo(host,"ruptime",&hint,&ailist) != 0)
70 {
71 printf("getaddrinfo error: %s",gai_strerror(err));
72 exit(-1);
73 }
74 for(aip = ailist;aip != NULL;aip = aip->ai_next)
75 {
76 if((sockfd = socket(aip->ai_family,SOCK_STREAM,0))<0)
77 err = errno;
78 else
79 printf("socket successfully.\n");
80 if(connect_retry(sockfd,aip->ai_addr,aip->ai_addrlen) < 0)
81 {err = errno;
82 printf("connect error\n");
83 }
84 else
85 {
86 printf("connect server successfully.\n");
87 printf("Receing time from server is: \n");
88 print_uptime(sockfd);
89 exit(0);
90 }
91 }
92 fprintf(stderr,"can't connect to %s: %s\n",host,strerror(err));
93 strerror(err);
94 exit(0);
95 }
服务器程序如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <arpa/inet.h>
5 #include <netdb.h>
6 #include <sys/socket.h>
7 #include <errno.h>
8 #include <syslog.h>
9 #include <string.h>
10
11 #define BUFLEN 128
12 #define QLEN 10
13 #ifndef Host_NAME_MAX
14 #define HOST_NAME_MAX 256
15 #endif
16
17 int initserver(int type,const struct sockaddr *addr,socklen_t alen,int qlen)
18 {
19 int fd;
20 int err = 0;
21 if((fd = socket(addr->sa_family,type,0)) < 0)
22 {
23 return -1;
24 }
25 printf("socket is created successfully.\n");
26 if(bind(fd,addr,alen) < 0)
27 {
28 err = errno;
29 goto errout;
30 }
31 printf("bind successfully.\n");
32 if(type == SOCK_STREAM || type == SOCK_SEQPACKET)
33 {
34 if(listen(fd,qlen) < 0)
35 {
36 err = errno;
37 goto errout;
38 }
39 printf("listen successfully.\n");
40 }
41 return fd;
42 errout:
43 close(fd);
44 errno = err;
45 return -1;
46 }
47
48 void serve(int sockfd)
49 {
50 int clfd;
51 FILE *fp;
52 char buf[BUFLEN];
53 printf("Prepareing for accpet client :\n");
54 for(;;)
55 {
56 clfd = accept(sockfd,NULL,NULL);
57 if(clfd < 0)
58 {
59 syslog(LOG_ERR,"ruptime: accpet error: %s",gai_strerror(errno));
60 exit(1);
61 }
62 printf("received a client request.\n");
63 //通过管道调用uptime
64 if((fp =popen("/usr/bin/uptime","r")) == NULL)
65 {
66 sprintf(buf,"error: %s\n",strerror(errno));
67 send(clfd,buf,strlen(buf),0);
68 }
69 else
70 {
71 while(fgets(buf,BUFLEN,fp) != NULL)
72 send(clfd,buf,strlen(buf),0);
73 pclose(fp);
74 }
75 close(clfd);
76 }
77 }
78 int main()
79 {
80 struct addrinfo *ailist,*aip;
81 struct addrinfo hint;
82 int sockfd;
83 int err,n;
84 char *host;
85 #ifdef _SC_HOST_NAME_MAX
86 n = sysconf(_SC_HOST_NAME_MAX);
87 if(n<0)
88 #endif
89 n = HOST_NAME_MAX;
90 host = (char*)malloc(n);
91 if(host == NULL)
92 {
93 perror("malloc() error");
94 exit(-1);
95 }
96 if(gethostname(host,n) < 0)
97 {
98 perror("gethostname() error");
99 exit(-1);
100 }
101 puts(host);
102 hint.ai_flags = AI_CANONNAME;
103 hint.ai_family = 0;
104 hint.ai_socktype = SOCK_STREAM;
105 hint.ai_protocol = 0;
106 hint.ai_addrlen = 0;
107 hint.ai_canonname = NULL;
108 hint.ai_addr = NULL;
109 hint.ai_next = NULL;
110 if(getaddrinfo(host,"ruptime",&hint,&ailist) != 0)
111 {
112 syslog(LOG_ERR,"getaddrinfo error: %s",gai_strerror(err));
113 exit(-1);
114 }
115 for(aip = ailist;aip != NULL;aip = aip->ai_next)
116 if((sockfd = initserver(SOCK_STREAM,aip->ai_addr,aip->ai_addrlen,QLEN)) >= 0)
117 {
118 serve(sockfd);
119 exit(0);
120 }
121 exit(-1);
122 }
为了获取ruptime的网络信息,需要编辑/etc/services文件,追加ruptime 4000/tcp 。
程序执行结果如下:
客户端获取服务器时间:
服务器端响应客户请求:
20、带外数据:TCP支持带外数据,但是UDP不支持。TCP仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。为了产生紧急数据,在三个send函数中任何一个指定MSG_OOB。如果带MSG_OOB标志传输字节超过一个时,最后一个字节被看作紧急数据字节。当接收到紧急数据时,那么改善信号SIGURG。
TCP支持紧急标记的概念:在普通数据流中紧急数据所在的位置。如果采用套接字选项SO_OOBINLINE,那么可以在普通数据中接收紧急数据。为帮助判断是否接收到紧急标记,可以使用函数sockatmark
#include <sys/socket.h>
Int sockatmark ( int sockfd);
当下一个要读的字节在紧急标志所标识的位置时,sockatmark返回1。当带外数据出现在套接字读取队列时,select函数会返回一个文件描述符并且拥有一个异常状态挂起。可以在普通数据流上接受紧急数据,或者在某个recv函数中MSG_OOB标志在其他队列数据之前接收紧急数据。TCP队列仅有一字节的紧急数据,如果在接收当前的紧急数据字节之前又有新的紧急数据到来,那么当前的字节会被丢弃。
21、在基于套接字异步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