linux-C编程-网络编程

3,linux网络编程

三元组(ip地址,协议,端口)就可以标识网络的进程

3.1,OSI七层模型和TCP/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 物理层,就是实实在在的物理链路,负责将数据以比特流的方式发送、接收,就不多说了。 


3.2,TCP握手

3.2.1 三次握手建立连接

在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
1,客户端向服务器发送一个SYN J
2,服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
3,客户端再想服务器发一个确认ACK K+1

linux-C编程-网络编程_第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返回,至此三次握手完毕,连接建立。

三次握手的目的是建立双向的连接,第一次握手是客户端向服务器端发出请求 
第二次握手是服务器端告诉客户端,第一次握手是成功的,即可以从客户端发送到客户端, 
第三次握手是客户端告诉服务器端,第二次握手是成功的,即可以从客户端到服务器端 
这样就保证了客户端和服务器端的双向通信,

3.2.2 四次握手释放连接

linux-C编程-网络编程_第2张图片

1,某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
2,另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
3,一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
4,接收到这个FIN的源发送端TCP对它进行确认。

3.3,socket编程

http://www.cnblogs.com/goodcandle/archive/2005/12/10/socket.html

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

3.3.1 AF_INET TCP传输最简单版本

linux-C编程-网络编程_第3张图片


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返回值没有区分,都是
<0 出错
=0 连接关闭
>0 接收到数据大小,

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

3.3.2 加入返回值检查和IP地址

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);
}

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 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);
}

3.3.3 AF_INET UDP传输最简单版本

linux-C编程-网络编程_第4张图片

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;
}


3.3.4 AF_INET UNIX本地传输最简单版本

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);
}

unix_server.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 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);
	}
}


3.4 网络编程函数

http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html
http://blog.csdn.net/simba888888/article/details/9012521

Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”

#include<sys/types.h>
#include<sys/socket.h>

3.4.1 函数socket

创建套接字:socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。
函数原型:
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表示出错
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

3.4.2 函数bind

命名套接字: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。

3.4.3 函数listen

创建套接字队列
函数原型:
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()处理。

3.4.4 函数accept

接受连接
函数原型:
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);


3.4.5 函数connect

函数原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
客户端连接服务器,addr为传入参数,表示目的服务器的(ip,protocal,port)。

3.4.6 函数read与write

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);

3.4.7 函数close

函数原型:
int close(int fd);

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

3.4.8 函数getsockname

函数返回与套接口关联的本地协议地址。

函数原型:
int getsockname(int sockfd, struct sockaddr * localaddr, socken_t * addrlen);

3.4.9 函数getpeername

函数返回与套接口关联的远程协议地址。

函数原型:
int getpeername(int sockfd,struct sockaddr* peeraddr,int* addrlen);
参数说明:
sockfd:是由socket()调用返回的套接口文件描述符。
peeraddr:传出数据结构sockaddr的指针,包括(IP,protocol,port)
addrlen:传出结构大小的指针
int:返回值,-1表示出错


3.5 网络字节序与主机字节序转换

将主机字节序转换为网络字节序(避免大端小端问题)
#include <netinet/in.h>

3.5.1 函数htonl

uint32_t htonl(uint32_t hostlong);

3.5.2 函数htons

uint16_t htons(uint16_t hostshort);

3.5.3 函数ntohl

uint32_t ntohl(uint32_t netlong);

3.5.4 函数ntohs

uint16_t ntohs(uint16_t netshort);

3.6 IP地址与主机字节序转换

3.6.1 函数inet_pton

[将“点分十进制” -> “整数”]

函数原型:
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。

3.6.2 函数inet_ntop

[将“整数” -> “点分十进制”]

函数原型:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);

3.7 主机数据库函数

3.7.1 函数gethostname

它返回(本地)计算机的名字,存储在hostname中,大小为size

函数原型:
int gethostname(char*hostname,size_tsize);
参数说明:
int:返回值gethostname将返回0。如果失败,它将返回-1。 

3.7.2 函数gethostbyname

根据域名或者主机名获取信息

函数原型:
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);
} 

3.7.3 函数gethostbyaddr

根据ip地址(网络字节序)获取信息

函数原型:
struct hostent *gethostbyaddr(const char *name,int len,int type)
参数说明:
name:ip地址,例如: inet_addr("192.168.4.111")
len:
type:
hostend:返回值。如果函数调用失败,将返回NULL。

3.7.4 函数getservbyname

用于根据给定的名字来查找相应的服务器,返回对应于给定服务名和协议名的相关服务信息

函数原型:
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*/
}

3.7.5 函数getservbyport

返回与给定服务名对应的包含名字和服务号信息的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);
} 


3.5 多客户编程

http://blog.csdn.net/simba888888/article/details/9034407

3.5.1 阻塞与非阻塞

(1)阻塞block
    所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞(进程Sleep),函数不能立即返回。
    例如socket编程中connect、accept、recv、recvfrom这样的阻塞程序。
    再如绝大多数的函数调用、语句执行,严格来说,他们都是以阻塞方式执行的。
(2)非阻塞non-block
    所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。
   非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。?????

3.5.2 函数select

函数原型:
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或者时间耗尽,函数均返回。
2,若timeout设置为t=0,表示非阻塞,函数检查完每个fd后立即返回。
3,若timeout设置为t="NULL",表示阻塞,直到有一个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( );
       }
}

3.5.3 函数pselect

函数原型:
#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的区别在于:
1,pselect使用timespec结构指定超时值。timespec结构以秒和纳秒表示时间,而非秒和微秒。
2,pselect的超时值被声明为const,这保证了调用pselect不会改变timespec结构。
3,pselect可使用一个可选择的信号屏蔽字。在调用pselect时,以原子操作的方式安装该信号屏蔽字,在返回时恢复以前的信号屏蔽字。

3.5.4 函数poll

函数原型:
#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中。

3.5.5 函数epoll

一共有三个函数:

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()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事。
struct epoll_event结构如下:
struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
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);








你可能感兴趣的:(linux-C编程-网络编程)