Linux C编程-网络编程2-面向连接的网络编程
由于之前已经写过一篇介绍Socket通信的文章,故这篇文章是在那一篇的基础上继续补充完善,另一篇的链接如下:
http://blog.****.net/dlutbrucezhang/article/details/8577810
其中介绍各个函数,接下来介绍其他的一些常用系统函数。
首先给出一个实例说明常用函数的用法。
编写一个客户机--服务器程序,其中客户机使用流套接字向服务器请求日期和时间,服务器在收到请求之后,回答请求并显示客户的地址。
服务器程序如下:
#include <time.h> #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define LISTENQ 5 #define MAXLINE 512 int main() { int listenfd, connfd; socklen_t len; struct sockaddr_in servaddr, cliaddr; char buff[MAXLINE]; time_t ticks; listenfd=socket(AF_INET, SOCK_STREAM,0); if(listenfd<0) { printf("Socket created failed.\n"); return -1; } servaddr.sin_family=AF_INET; servaddr.sin_port=htons(6666); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0) { printf("bind failed.\n"); return -1; } printf("listening....\n"); listen(listenfd, LISTENQ); while(1) { len=sizeof(cliaddr); connfd=accept(listenfd,(struct sockaddr *)&cliaddr, &len); printf("connect from %s, port %d \n",inet_ntoa(cliaddr.sin_addr.s_addr),ntohs(cliaddr.sin_port)); ticks=time(NULL); sprintf(buff,"% .24s \r \n",ctime(&ticks)); write(connfd,buff,strlen(buff)); close(connfd); } }
客户机程序如下:
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define MAXBUFFSIZE 256 #define PORT 6666 #define HOST_ADDR "127.0.0.1" int main(int argc, char *argv[]) { int sockfd,n; char recvbuff[MAXBUFFSIZE]; struct sockaddr_in servaddr; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { printf("Socket created failed.\n"); return -1; } servaddr.sin_family=AF_INET; servaddr.sin_port=htons(6666); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); printf("connecting...\n"); if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0) { printf("Connect server failed.\n"); return -1; } while((n=read(sockfd,recvbuff,MAXBUFFSIZE))>0) { recvbuff[n]=0; fputs(recvbuff,stdout); } if(n<0) { printf("Read failed!\n"); return -2; } return 0; }
下面重点介绍其他一些套接字操作函数:
getsockname和getpeername
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
返回:0—OK,-1—出错。
getsockname函数返回与套接口关联的本地协议地址。
getpeername函数返回与套接口关联的远程协议地址。
使用场合:
- 在不调用bind的TCP客户,当connect成功返回后,getsockname返回分配给此连接的本地IP地址和本地端口号;
- 在以端口号为0调用bind后,使用getsockname返回内核分配的本地端口号;
- getsockname可用来获取某套接口的地址族;
- 在捆绑了通配IP地址的TCP服务器上,当连接建立后,可以使用getsockname获得分配给此连接的本地IP地址;
- 当一个服务器调用exec启动后,他获得客户身份的唯一途径是调用getpeername函数。
下面给出实例说明上述函数的用法:
1.getsockname的用法,它打印出用默认地址命名的套接字的实际地址。
#include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> int main() { int sockfd; socklen_t len; struct sockaddr_in addr, raddr; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { printf("create socket failed!\n"); return -1; } addr.sin_family=AF_INET; addr.sin_port=htons(0); addr.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(sockfd,(struct sockaddr *)&addr,sizeof(addr))<0) { printf("bind socket failed!\n"); return -2; } len=sizeof(raddr); if(getsockname(sockfd,(struct sockaddr *)&raddr,&len)<0) { printf("getsockname failed!\n"); return -3; } printf("bound name= %s, port =%d \n", ntohl(raddr.sin_addr.s_addr),ntohs(raddr.sin_port)); return 0; }
2.服务进程的套接字是被动套接字,它可以接收连接,但无法选择连接的对象。但有时为了拒绝那些不希望的连接,需要知道连接从何而来,并且当不希望与那个进程交谈时关闭该连接。下面的程序说明了如何使用getpeername来查看对等套接字,并在不希望与之交谈时关闭与它的连接。
int check_peer(int sockfd, in_addr_t *refuselist) { socklen_t len; struct sockaddr_in addr, raddr; in_addr_t s_addr; len=sizeof(raddr); if(getpeername(sockfd,(struct sockaddr *)raddr, &len)<0) { printf("getpeername with socket %d failed.\n",sockfd); return -1; } s_addr=raddr.sin_addr.s_addr; ap=refuselist; for(; ap!=0;ap++) { close(sockfd); return -1; } return 0; }
send和recv函数
1 #include <sys/socket.h>
2 ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
3 ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
recv 和send的前3个参数等同于read和write。
flags参数值为0或:
flags | 说明 | recv | send |
MSG_DONTROUTE | 绕过路由表查找 | • | |
MSG_DONTWAIT | 仅本操作非阻塞 | • | • |
MSG_OOB | 发送或接收带外数据 | • | • |
MSG_PEEK | 窥看外来消息 | • | |
MSG_WAITALL | 等待所有数据 | • |
1. send解析
sockfd:指定发送端套接字描述符。
buff: 存放要发送数据的缓冲区
nbytes: 实际要改善的数据的字节数
flags: 一般设置为0
1) send先比较发送数据的长度nbytes和套接字sockfd的发送缓冲区的长度,如果nbytes > 套接字sockfd的发送缓冲区的长度, 该函数返回SOCKET_ERROR;
2) 如果nbtyes <= 套接字sockfd的发送缓冲区的长度,那么send先检查协议是否正在发送sockfd的发送缓冲区中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送sockfd的发送缓冲区中的数据或者sockfd的发送缓冲区中没有数据,那么send就比较sockfd的发送缓冲区的剩余空间和nbytes
3) 如果 nbytes > 套接字sockfd的发送缓冲区剩余空间的长度,send就一起等待协议把套接字sockfd的发送缓冲区中的数据发送完
4) 如果 nbytes < 套接字sockfd的发送缓冲区剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把套接字sockfd的发送缓冲区中的数据传到连接的另一端的,而是协议传送的,send仅仅是把buf中的数据copy到套接字sockfd的发送缓冲区的剩余空间里)。
5) 如果send函数copy成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR; 如果在等待协议传送数据时网络断开,send函数也返回SOCKET_ERROR。
6) send函数把buff中的数据成功copy到sockfd的改善缓冲区的剩余空间后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。(每一个除send的socket函数在执行的最开始总要先等待套接字的发送缓冲区中的数据被协议传递完毕才能继续,如果在等待时出现网络错误那么该socket函数就返回SOCKET_ERROR)
7) 在unix系统下,如果send在等待协议传送数据时网络断开,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的处理是进程终止。
2.recv函数
sockfd: 接收端套接字描述符
buff: 用来存放recv函数接收到的数据的缓冲区
nbytes: 指明buff的长度
flags: 一般置为0
1) recv先等待s的发送缓冲区的数据被协议传送完毕,如果协议在传送sock的发送缓冲区中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR
2) 如果套接字sockfd的发送缓冲区中没有数据或者数据被协议成功发送完毕后,recv先检查套接字sockfd的接收缓冲区,如果sockfd的接收缓冲区中没有数据或者协议正在接收数据,那么recv就一起等待,直到把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲区中的数据copy到buff中(注意协议接收到的数据可能大于buff的长度,所以在这种情况下要调用几次recv函数才能把sockfd的接收缓冲区中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)
3) recv函数返回其实际copy的字节数,如果recv在copy时出错,那么它返回SOCKET_ERROR。如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
4) 在unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用 recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
下面给出实例程序,说明函数的用法。
这是一个简单的发送带外数据的程序。
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define PORT 6666 #define HOST_ADDR "127.0.0.1" int main() { int sockfd,n; struct sockaddr_in servaddr; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { printf("Socket created failed.\n"); return -1; } servaddr.sin_family=AF_INET; servaddr.sin_port=htons(6666); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); printf("connecting...\n"); if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0) { printf("Connect Error.\n"); return -1; } write(sockfd,"123",3); printf("wrote 3 byte of normal data. \n"); sleep(1); send(sockfd,"a",1,MSG_OOB); printf("wrote 1 byte of OOB data.\n"); sleep(1); write(sockfd,"56",2); printf("wrote 2 byte of normal data \n"); sleep(1); send(sockfd,"b",1,MSG_OOB); printf("wrote 1 byte of OOB data\n"); sleep(1); write(sockfd,"89",2); printf("wrote 2 byte of normal data \n"); sleep(1); return 0; }
接收带外数据的例子。
#include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <signal.h> #include <fcntl.h> #include <unistd.h> #define LISTENQ 5 int listenfd, connfd; void sig_urg(int signo); int main() { int n; char buff[100]; socklen_t len; struct sockaddr_in servaddr, cliaddr; struct sigaction action; action.sa_handler=sig_urg; sigemptyset(&action.sa_mask); action.sa_flags=0; listenfd=socket(AF_INET, SOCK_STREAM,0); if(listenfd<0) { printf("Socket created failed.\n"); return -1; } servaddr.sin_family=AF_INET; servaddr.sin_port=htons(6666); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0) { printf("bind failed.\n"); return -1; } printf("listening....\n"); listen(listenfd, LISTENQ); len=sizeof(cliaddr); connfd=accept(listenfd,(struct sockaddr *)&cliaddr, &len); if(sigaction(SIGURG,&action, NULL)==-1) { printf("Couldn't register signal handler.\n"); return -2; } fcntl(connfd,F_SETOWN,getpid()); while(1) { if((n=read(connfd,buff,sizeof(buff)))==0) { printf("received EOF \n"); return 0; } buff[n]=0; printf("read %d bytes: %s \n",n,buff); } } void sig_urg(int signo) { int n; char buff[100]; printf("SIGURG received \n"); n=recv(connfd,buff,sizeof(buff),MSG_OOB); buff[n]=0; printf("read %d OOB byte: %s\n",n,buff); }
注:
带外数据:
传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方。为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道。linux系统的套接字机制支持低层协议发送和接受带外数据。但是TCP协议没有真正意义上的带外数据。为了发送重要协议,TCP提供了一种称为紧急模式(urgent mode)的机制。TCP协议在数据段中设置URG位,表示进入紧急模式。接收方可以对紧急模式采取特殊的处理。很容易看出来,这种方式数据不容易被阻塞,并且可以通过在我们的服务器端程序里面捕捉SIGURG信号来及时接受数据。这正是我们所要求的效果。