三元组(ip地址,协议,端口)就可以标识网络的进程
OSI七层网络模型由下至上为1至7层,分别为:
物理层(Physical layer),数据链路层(Data link layer),网络层(Network layer),传输层(Transport layer),会话层(Session layer),表示层(Presentation layer),应用层(Application layer)。
1.1 应用层,很简单,就是应用程序。这一层负责确定通信对象,并确保由足够的资源用于通信,这些当然都是想要通信的应用程序干的事情。
1.2 表示层,负责数据的编码、转化,确保应用层的正常工作。这一层,是将我们看到的界面与二进制间互相转化的地方,就是我们的语言与机器语言间的转化。数据的压缩、解压,加密、解密都发生在这一层。这一层根据不同的应用目的将数据处理为不同的格式,表现出来就是我们看到的各种各样的文件扩展名。
1.3 会话层,负责建立、维护、控制会话,区分不同的会话,以及提供单工(Simplex)、半双工(Half duplex)、全双工(Full duplex)三种通信模式的服务。我们平时所知的NFS,RPC,X Windows等都工作在这一层。
1.4 传输层,负责分割、组合数据,实现端到端的逻辑连接。数据在上三层是整体的,到了这一层开始被分割,这一层分割后的数据被称为段(Segment)。三次握手(Three-way handshake),面向连接(Connection-Oriented)或非面向连接(Connectionless-Oriented)的服务,流控(Flow control)等都发生在这一层。
1.5 网络层,负责管理网络地址,定位设备,决定路由。我们所熟知的IP地址和路由器就是工作在这一层。上层的数据段在这一层被分割,封装后叫做包(Packet),包有两种,一种叫做用户数据包(Data packets),是上层传下来的用户数据;另一种叫路由更新包(Route update packets),是直接由路由器发出来的,用来和其他路由器进行路由信息的交换。
1.6 数据链路层,负责准备物理传输,CRC校验,错误通知,网络拓扑,流控等。我们所熟知的MAC地址和交换机都工作在这一层。上层传下来的包在这一层被分割封装后叫做帧(Frame)。
1.7 物理层,就是实实在在的物理链路,负责将数据以比特流的方式发送、接收,就不多说了。
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
1,客户端向服务器发送一个SYN J
2,服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
3,客户端再想服务器发一个确认ACK K+1
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
三次握手的目的是建立双向的连接,第一次握手是客户端向服务器端发出请求
第二次握手是服务器端告诉客户端,第一次握手是成功的,即可以从客户端发送到客户端,
第三次握手是客户端告诉服务器端,第二次握手是成功的,即可以从客户端到服务器端
这样就保证了客户端和服务器端的双向通信,
1,某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
2,另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
3,一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
4,接收到这个FIN的源发送端TCP对它进行确认。
http://www.cnblogs.com/goodcandle/archive/2005/12/10/socket.html
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
tcp_client.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 6666 #define BUFF_SIZE 1024 #define MAXLEN 4096 main(int argc, char** argv) { if(argc!=2){ printf("usage: ./client <ipaddress>\n"); exit(0); } char sendline[4096]; //socket()建立socket int sockfd = socket(AF_INET,SOCK_STREAM,0); //要连接的服务器地址 struct sockaddr_in sliaddr; sliaddr.sin_family = AF_INET; sliaddr.sin_port = htons(PORT); inet_pton(AF_INET, argv[1], &sliaddr.sin_addr); //connect()发送请求(ip=argv[1],protocal=TCP,port=6666) connect(sockfd, (struct sockaddr*)&sliaddr, sizeof(sliaddr)); //recv sth recv_len = recv(sockfd, buff, sizeof(buff), 0); buff[recv_len] = '\0'; printf(" %s ", buff); //interactive while (1) { printf("Enter string to send: "); scanf("%s", buff); if (!strcmp(buff, "quit")) break; send_len = send(sockfd, buff, strlen(buff), 0); recv_len = recv(sockfd, buff, BUFF_SIZE, 0); buff[recv_len] = '\0'; printf(" received: %s \n", buff); } //close()关闭连接 close(sockfd); }
tcp_server.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 6666 #define WAIT_QUEUE_LEN 5 #define BUFF_SIZE 1024 #define WELCOME "Welcome to my server ^_^!\n" main() { int connfd; char buff[MAXLEN]; int len; //socket() 建立socket,其中SOCK_STREAM表示tcp连接 int sockfd = socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT); //bind()绑定一个socket(ip=all,protocal=TCP,port=6666) bind(sockfd,(struct sockaddr *) &servaddr, sizeof(servaddr)); //listen()监听 listen(sockfd, WAIT_QUEUE_LEN); //accept() & close() printf("======waiting for client's request======\n"); while(1){ c_addrlen = sizeof(struct sockaddr_in); connfd = accept(serverfd, (struct sockaddr *)&caddr, &c_addrlen); printf("client: ip=%s,port=%s\n", cliaddr.sin_addr.s_addr,cliaddr.sin_port); //send a welcome send(connfd, WELCOME, strlen(WELCOME), 0); //阻塞模式下recv==0表示客户端已断开连接 while ((len = recv(connfd, buff, BUFF_SIZE, 0)) > 0) { buff[len] = '\0'; printf("recv msg is : %s \n", buff); send(connfd, buff, len, 0); } close(connfd); } //close()关闭连接 close(sockfd); }阻塞与非阻塞recv返回值没有区分,都是
makefile
.PHONY : main main : server client server : server.o gcc -g -o server server.o client : client.o gcc -g -o client client.o server.o : server.c gcc -g -c server.c client.o : client.c gcc -g -c client.c clean : rm -rf *.o ser : ./server cli : ./client
tcp_client.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLEN 4096 main(int argc, char** argv) { if(argc!=2){ printf("usage: ./client <ipaddress>\n"); exit(0); } char sendline[4096]; //socket()建立socket int sockfd; if((sockfd=socket(AF_INET,SOCK_STREAM,0)) ==-1){ printf("create socket error: %s(errno: %d)\n", strerror(errno),errno); } struct sockaddr_in cliaddr; cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(6666); if(inet_pton(AF_INET, argv[1], &cliaddr.sin_addr)==-1){ printf("inet_pton error for %s\n",argv[1]); exit(0); } //connect()发送请求(ip=argv[1],protocal=TCP,port=6666) if(connect(sockfd, (struct sockaddr*)&cliaddr, sizeof(cliaddr))==-1){ printf("connect error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } printf("send msg to server: \n"); fgets(sendline, 4096, stdin); //send()发送数据 if(send(sockfd, sendline, strlen(sendline), 0)==-1){ printf("send msg error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } //close()关闭连接 close(sockfd); }
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLEN 4096 main() { int connfd; char buff[MAXLEN]; int n; //socket()建立socket int sockfd; if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){ printf("create socket error: %s(errno:%d)\n",strerror(errno),errno); exit(0); } struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(6666); //bind()绑定一个socket(ip=all,protocal=TCP,port=6666) if(bind(sockfd,(struct sockaddr *) &servaddr, sizeof(servaddr))==-1){ printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } //listen()监听 if(listen(sockfd,10)==-1){ printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } //accept() & close() printf("======waiting for client's request======\n"); while(1){ if((connfd=accept(sockfd, (struct sockaddr *)NULL,NULL))==-1){ printf("accept socket error: %s(errno: %d)",strerror(errno),errno); continue; } struct sockaddr_in serv, guest; char serv_ip[20]; char guest_ip[20]; int serv_len = sizeof(serv); int guest_len = sizeof(guest); getsockname(connfd, (struct sockaddr *)&serv, &serv_len); getpeername(connfd, (struct sockaddr *)&guest, &guest_len); inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip)); inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip)); printf("host %s:%d guest %s:%d\n", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port)); n = recv(connfd, buff, MAXLEN,0); buff[n] = '\0'; printf("recv msg from client: %s\n", buff); close(connfd); } //close()关闭连接 close(sockfd); }
udp_client.c
/** * @file: udpclient.c * @brief: A simple Udp server * @author: ToakMa <[email protected]> * @date: 2014/10/09 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define BUFF_SIZE 1024 #define PORT 9988 int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in remote_addr; int len; char buff[BUFF_SIZE]; //1. create a socket sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == sockfd) { perror("udp client socket: "); return -1; } //2. prepare ip and port memset(&remote_addr, 0, sizeof(remote_addr)); remote_addr.sin_family = AF_INET; remote_addr.sin_port = htons(PORT); remote_addr.sin_addr.s_addr = inet_addr(argv[1]); bzero(&(remote_addr.sin_zero), 8); //3. sendto strcpy(buff, "this a test\n"); printf("sending : %s\n", buff); len = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr)); if (len < 0) { perror("udp client sendto :"); return -1; } //4. close close(sockfd); return 0; }
udp_server.c
/** * @file: udpserver.c * @brief: A simple Udp server * @author: ToakMa <[email protected]> * @date: 2014/10/09 */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 9988 #define BUFF_SIZE 1024 int main(int argc, char *argv[]) { int sockfd; int sin_len; struct sockaddr_in saddr; struct sockaddr_in remote_addr; char buff[BUFF_SIZE]; int res, len; //1. create socket sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == sockfd) { perror("Udp server socket: "); return -1; } printf("Udp server socket create succ!\n"); //2. prepare IP and port memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(PORT); saddr.sin_addr.s_addr = INADDR_ANY; bzero(saddr.sin_zero, 8); //3. bind res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)); if (-1 == res) { perror("udp server bind: "); return -1; } //4. recvfrom printf("Wait for a packet ...\n"); sin_len = sizeof(struct sockaddr_in); len = recvfrom(sockfd, buff, BUFF_SIZE, 0, (struct sockaddr *)&remote_addr, &sin_len); if (-1 == len) { perror("udp server recvform: "); return -1; } buff[len] = '\0'; printf("Recived packet from %s, contents is: %s \n", \ inet_ntoa(remote_addr.sin_addr), buff); //5. close close(sockfd); return 0; }
unix_client.c
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <sys/un.h> #include <unistd.h> #include <errno.h> int main() { int result; char ch = 'A'; //创建socket int sockfd; if((sockfd = socket(AF_UNIX,SOCK_STREAM,0) == -1){ printf("create socket error: %s(errno: %d)\n", strerror(errno),errno); } struct sockaddr_un address; address.sun_family = AF_UNIX; strcpy(address.sun_path,"server_socket"); int len = sizeof(address); //connect() int result; if((result = connect(socket,(struct sockaddr*)&address,len)==-1){ printf("connect error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } //read & write write(sockfd,&ch,1); read(sockfd,&ch,1); printf("char from server = %c\n",ch); //close() close(sockfd); exit(0); }
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <sys/un.h> #include <unistd.h> #include <errno.h> int main() { int server_len,client_len; struct socket_un server_address; struct socket_un client_address; //删除以前套接字 unlink("server_socket"); //socket int server_sockfd = socket(AF_UNIX,SOCK_STREAM,0); server_address.sun_family = AF_UNIX; strcpy(server_address.sun_path,"server_socket"); server_len = sizeof(server_address); //bind() bind(server_sockfd,(struct sockaddr*)&server_address,server_len); //listen() listen(server_sockfd,5); while(1){ char ch; printf("server waiting\n"); client_len = sizeof(client_address); client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address,&client_len); //read & write read(client_sockfd,&ch,1); ch++; write(client_sockfd,&ch,1); close(client_sockfd); } }
Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”
#include<sys/types.h> #include<sys/socket.h>
函数原型: int socket(int domain, int type, int protocol); 参数说明: domain:即协议域。常用的协议族有,AF_INET(ipv4)、AF_INET6(ipv6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。 type:指定socket类型。常用的socket类型有,SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。 protocol:指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。 int:返回值为-1表示出错
命名套接字:bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
函数原型: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 参数说明: sockfd:是由socket()调用返回的套接口文件描述符。 addr:传入数据结构sockaddr的指针,包括(IP,protocol,port)需要转换为通用地址类型struct sockaddr* addrlen:以设置成sizeof(struct sockaddr) int:返回值,-1表示出错
ipv4地址结构【AF_INET】: struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };ipv6地址结构【AF_INET6】:
struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };Unix域地址结构【AF_UNIX】:
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ 使用strcpy(address.sun_path,"server_socket") };通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;所以服务器端在listen之前会调用bind()。
而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合,在connect()时由系统随机生成一个。由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。注意,客户端不是不允许调用bind(),只是没有必要调用
bind()固定一个端口号,服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
对server端的addr的初始化如下所示:
memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY);首先将整个结构体清零(也可以用bzero函数),然后设置地址类型为AF_INET, 网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为5188。
函数原型: int listen(int sockfd, int backlog); 参数说明: sockfd:即为要监听的socket描述字 backlog:相应socket可以排队的最大连接个数
第二个参数是进入队列中允许的连接的个数。进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为20。你可以设置为5或者10。当出错时,listen()将会返回-1值。
这个函数对于backlog的解释《unix网络编程》P85是说已完成连接队列(ESTABLISHED)和未完成连接队列(SYN_RCVD)之和(即等待连接数而非连接数)。服务器调用的accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略。在计算机早期由于网络连接的处理速度很慢,几个并发的请求就可能导致系统处理不过来而引发错误,现在这个数字的意义已经不大了,现在软硬件性能几乎能保证这个队列不可能满。连接的状态变为ESTABLISHED之后(实际是连接由从半连接队列转移到了完成握手的完成队列)才表示可以被accpet()处理。
函数原型: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 参数说明: sockfd:是由socket()调用返回的套接口文件描述符。 addr:传出连接客户的sockaddr指针,包括(IP,protocol,port) addrlen:传入指定客户结构addr的长度,返回时该值传出为连接客户地址addr的实际长度 int:返回值,-1表示出错
调用accept()之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接来说,你就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描述符可以用来调用send()和recv()。
可以通过对套接字文件描述符设置O_NONBLOCK来改变其是否阻塞:
int flags = fcntl(socket, F_GETFL,0); fcntl(socket, F_SETFL, O_NONBLOCK| flags);
函数原型: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);客户端连接服务器,addr为传入参数,表示目的服务器的(ip,protocal,port)。
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); #include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, 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 recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
函数原型: int close(int fd);
注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
函数返回与套接口关联的本地协议地址。
函数原型: int getsockname(int sockfd, struct sockaddr * localaddr, socken_t * addrlen);
函数原型: int getpeername(int sockfd,struct sockaddr* peeraddr,int* addrlen); 参数说明: sockfd:是由socket()调用返回的套接口文件描述符。 peeraddr:传出数据结构sockaddr的指针,包括(IP,protocol,port) addrlen:传出结构大小的指针 int:返回值,-1表示出错
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
[将“点分十进制” -> “整数”]
函数原型: int inet_pton(int af, const char *src, void *dst); 参数说明: af:地址族,AF_INET为ipv4地址,AF_INET6为ipv6地址 src:为ip地址(ipv4例如1.1.1.1) dst:函数将该地址转换为in_addr的结构体,并复制在*dst中 int:返回值:如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0。
[将“整数” -> “点分十进制”]
函数原型: const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
它返回(本地)计算机的名字,存储在hostname中,大小为size
函数原型: int gethostname(char*hostname,size_tsize); 参数说明: int:返回值gethostname将返回0。如果失败,它将返回-1。
根据域名或者主机名获取信息
函数原型: struct hostent *gethostbyname(const char *name); 参数说明: name:主机名,如"www.baidu.com" hostend:返回值。如果函数调用失败,将返回NULL。
hostend的结构如下:
struct hostent { char *h_name; //表示的是主机的规范名,例如www.google.com的规范名其实是www.l.google.com char **h_aliases; //表示的是主机的别名 int h_addrtype; //IP地址的类型 int h_length; //IP地址的长度 char **h_addr_list; //主机的ip地址,注意这是以网络字节顺序保存的一个值,用inet_ntop恢复 };
示例代码:
#include <netdb.h> #include <sys/socket.h> #include <stdio.h> #include <unistd.h> int main(int argc, char **argv) { char *host,**names; struct hostent *hostinfo; cha str[32]; /* 取得命令后第一个参数,即要解析的域名或主机名 */ if(argc==1){ char myname[256]; gethostname(myname,255); host=myname; } else{ host=argv[1]; } /* 调用gethostbyname()。调用结果都存在hostinfo中 */ if( (hostinfo = gethostbyname(host) ) == NULL ) { printf("gethostbyname error for host:%s/n", host); exit(1); /* 如果调用gethostbyname发生错误,返回1 */ } /* 将主机的规范名打出来 */ printf("official hostname:%s/n",hostinfo->h_name); /* 主机可能有多个别名,将所有别名分别打出来 */ for(names = hostinfo->h_aliases; *names != NULL; names++) printf(" alias:%s/n",*names); /* 根据地址类型,将地址打出来 */ switch(hostinfo->h_addrtype) { case AF_INET: case AF_INET6: names=hostinfo->h_addr_list; /* 将刚才得到的所有地址都打出来。其中调用了inet_ntop()函数 */ for(;*names!=NULL;names++) printf(" address:%s/n", inet_ntop(hostinfo->h_addrtype, *names, str, sizeof(str))); break; default: printf("unknown address type/n"); break; } exit(0); }
根据ip地址(网络字节序)获取信息
函数原型: struct hostent *gethostbyaddr(const char *name,int len,int type) 参数说明: name:ip地址,例如: inet_addr("192.168.4.111") len: type: hostend:返回值。如果函数调用失败,将返回NULL。
用于根据给定的名字来查找相应的服务器,返回对应于给定服务名和协议名的相关服务信息
函数原型: struct servernt *gerservbyname(const char *servname,const char *protoname) 参数说明: servname:例如smtp protoname:例如tcp
servent结构如下:
struct servent{ char *s_name; /*服务的正规名字*/ char **s_aliases;/*别名列表*/ int s_port; /*服务的端口号*/ char *s_proto; /*服务的协议,如tcp或udp*/ }
返回与给定服务名对应的包含名字和服务号信息的servent结构指针(注意参数port的值必须是网络字节序类型的,所以在使用的时候需要使用函数htons(port)来进行转换)。
函数原型: struct servent *getservbyport(int port,const char *proroname); 函数示例: sptr=getservbyport(htons(53),"udp");
示例代码:
#获取任意已知主机的日期和时间 #include <netdb.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> int main(int argc, char **argv) { char *host; struct hostent * hostinfo; struct servent * servinfo; /* 取得命令后第一个参数,即要解析的域名或主机名 */ if(argc==1){ host="localhost"; } else{ host=argv[1]; } /* 调用gethostbyname()。调用结果都存在hostinfo中 */ if( (hostinfo = gethostbyname(host) ) == NULL ) { printf("gethostbyname error for host:%s/n", host); exit(1); /* 如果调用gethostbyname发生错误,返回1 */ } if ( (servinfo = getservbyname("daytime","tcp")) ); { printf("no daytime service/n"); exit(1); /* 如果调用getservbyname发生错误,返回1 */ } /* 将daytime的端口打印出来*/ printf("daytime port is %d/n",ntohs(servinfo -> s_port)); int sockfd = socket(AF_INET, SOCK_STREAM,0); struct sockaddr_in address; address.sin_family = AF_INET; address.sin_port = servinfo -> s_port; address.sin_addr= *(struct in_addr*)*hostinfo->h_addr_list; int len = sizeof(address); int result; if((result = connect(socket,(struct sockaddr*)&address,len))==-1){ printf("connect error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } char buffer[128]; result = read(sockfd,buffer,sizeof(buffer)); buffer[result]='\0'; printf("read %d bytes: %s",result,buffer); close(sockfd); exit(0); }
http://blog.csdn.net/simba888888/article/details/9034407
(1)阻塞block
所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞(进程Sleep),函数不能立即返回。
例如socket编程中connect、accept、recv、recvfrom这样的阻塞程序。
再如绝大多数的函数调用、语句执行,严格来说,他们都是以阻塞方式执行的。
(2)非阻塞non-block
所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。
非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。?????
函数原型: int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 参数说明: nfds:为文件描述符集合中的最大值+1,避免函数检查fd_set的所有1024位 readfds:被监控是否可以读 writefds:被监控是否可以写 exceptfds:被监控是否发生异常 timeout:超时时间,Linux返回时会被修改成剩余的时间。 int:返回值,返回状态发生变化的描述符的总数,错误返回-1,超时返回0。
关于timeout:
struct timeval结构如下: struct timeval { long tv_sec; //seconds long tv_usec; //microseconds };1,若timeout设置为t>0,表示等待固定时间,有一个fd位被置为1或者时间耗尽,函数均返回。
相关操作: FD_ZERO(fd_set *set); //fd_set很多系统实现为bit arrays,将所有的文件描述符从fd_set中清空 FD_CLR(int fd, fd_set *set); //从set中清除fd FD_SET(int fd, fd_set *set); //将fd添加到set中 FD_ISSET(int fd, fd_set *set);//判断描述符fd是否在给定的描述符集set中,通常配合select函数使用,由于select函数成功返回时会将未准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。
常用代码:
fd_set rdfds; struct timeval tv; tv.tv_sec = 1; tv.tv_uses = 500; int ret; FD_ZERO(&rdfds); FD_SET(socket, &rdfds); ret = select (socket + 1, %rdfds, NULL, NULL, &tv); if(ret < 0) perror ("select"); else if (ret == 0) printf("time out"); else { printf(“ret = %d/n”,ret); if(FD_ISSET(socket, &rdfds)){ /* 读取socket句柄里的数据 */ recv( ); } }
函数原型: #include <sys/select.h> int pselect(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, const struct timespec *restrict tsptr,const sigset_t *restrict sigmask); 参数说明 返回值:Returns: count of ready descriptors, 0 on timeout, -1 on error它与select的区别在于:
函数原型: #include <sys/poll.h> int poll (struct pollfd *fds, unsigned int nfds, int timeout); 参数说明: timeout:【值=INFTIM】表示永远等待【值=0】表示立即返回,不阻塞进程【值>0】等待指定数目的毫秒数。
pollfd结构说明:
struct pollfd { int fd; /* 文件描述符 */ short events; /* 等待的事件 */ short revents; /* 发生的事件 */ }; poll函数可用的测试值: 常量 说明 POLLIN 普通或优先级带数据可读 POLLRDNORM 普通数据可读 POLLRDBAND 优先级带数据可读 POLLPRI 高优先级数据可读 POLLOUT 普通数据可写 POLLWRNORM 普通数据可写 POLLWRBAND 优先级带数据可写 POLLERR 发生错误 POLLHUP 发生挂起 POLLNVAL 描述字不是一个打开的文件 注意:后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。
一共有三个函数:
int epoll_create(int size);创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };events可以是以下几个宏的集合:
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有 说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
在前面讲过的客户/服务器程序中,服务器只能处理一个客户端的请求,如何同时服务多个客户端呢?在未讲到select/poll/epoll等高级IO之前,比较老土的办法是使用fork来实现。网络服务器通常用fork来同时服务多个客户端,父进程专门负责监听端口,每次accept一个新的客户端连接就fork出一个子进程专门服务这个客户端。但是子进程退出时会产生僵尸进程,父进程要注意处理SIGCHLD信号和调用wait清理僵尸进程,最简单的办法就是直接忽略SIGCHLD信号。
1,这里为什么会有僵尸进程?
2,实现P2P
3,signal(SIGCHLD, SIG_IGN);