网络IPC:套接字,实现UDP和TCP

之前我们学习了系统的进程间通信(IPC)机制:管道,消息队列,共享内存,信号量等,这些机制允许在同一台计算机中的两个进程间通信。而今天的网络进程间通信(网络IPC)实现了不同的计算机的两个进程间的通信。网络IPC实现的机制是网络套接字。

套接字是通信端点的抽象,通常用IP地址和端口号来描述。

IP地址表示接入网络的主机的标识,端口号表示该主机上唯一的进程。一对IP地址和端口号标识了计算机网络中的两个唯一的进程。

1. 创建套接字

正如文件描述符一样,应用程序用套接字描述符访问套接字。套接字描述符在系统中被当作是一种文件描述符。

#include <sys/socket.h>

int socket ( int domain,int type, int protocol );

参数domain(域)确定通信的特性,各个域都以常数AF_开头:

                                                    

参数type确定套接字的类型,进一步确定通信特征

                                                 

参数protocol通常是0.表示为给定的域和套接字类型选择默认协议。例如:在AF_INET域中的SOCK_STREAM的默认协议为TCP(传输控制协议),在AF_INET域中的SOCK_DGREAM的默认协议为UDP(用户数据报协议)。

注:数据报提供了无连接的服务,字节流(SOCK_TREAM)要求在交换数据之前,在本地套接字和通信的进程的套接字之间建立一个逻辑连接。

注意:调用socket创建套接字和打开文件一样,在创建完毕,需要调用close函数关闭对文件或套接字的访问。

补充:套接字通信是双向的,可以采用shutdown来禁止一个套接字的IO。

#include<sys/socket.h>
int shutdown( int sockfd,int how);

参数how 是SHUT_RD时,关闭读端,是SHUT_WR时,关闭写端,是SHUT_RDWR时,关闭读写两端。

2. 网络字节序

与同一台计算机的进程通信时,不需要考虑字节序的问题,但是在网络中,我们需要考虑传输过程中的数据问题,就需要考虑字节序列。

字节序是由处理器架构决定的,一般情况下,我们不能改变。所谓的字节序是指计算机存储数据是以大端存储还是小端存储。

大端存储:数据的低字节位放置在内存的高地址处,数据的高位放置在内存的低地址处;

小端存储:数据的低位放置在内存的低地址处,数据的高位放置在内存的高地址处。

在网络传输中,我们规定以下几点:

(1)通常将缓冲区的数据按内存地址从低到高的顺序发出;

(2)接收主机将接收缓冲区的数据按内存地址从低到高的顺序取出;

(3)TCP/IP参考模型规定,网络数据流采用大端字节序;

为了使数据能被正确的传送和接收,系统提高了以下函数做网络字节序和主机字节序的转化:


注:这些函数很好记,h表示“主机”字节序,n表示“网络"字节序,s表示”短“整型,l表示”长“整型。

3. 地址格式

一个地址标识一个特定通信域的套接字端点,地址格式与通信域相关。为使不同的格式地址能够传入到套接字函数中,地址会被强制转化为通用的地址结构sockaddr:

struct sockaddr{
sa_family_t      sa_family;
char             sa_data[];}

在IPv4因特网域中(AF_INET),套接字地址结构用sockaddr_in表示:

struct sockaddr_in{
sa_family_t    sin_family;
in_port_t        sin_port;
struct  in_addr     sin_addr;
}

struct sin_addr{
in_addr_t    s_addr;
}

4.  将套接字与地址关联

对于客户端的套接字,可以让系统选一个默认的地址。然而,对于服务器端,需要给服务器套接字一个众所周知的地址,并且客户端应有一种方法知道连接服务器的地址,最简单的方法就是服务器保留一个地址并且注册在/etc/services或者某个名字服务中。

使用bind函数来关联地址和套接字:

#include <sys/socket.h>
int bind( int sock,const struct sockaddr* addr, socklen_t len);
返回值:若成功,返回0;若出错,返回-1;
注:对于使用的地址有以下限制:

    (1)在进程正在运行的计算机上,指定的地址必须有效,不能指定其他机器的地址。

    (2)地址必须和创建套接字的地址族格式匹配。

    (3)地址中的端口号不能小于1024.

   (4)对于因特网域,如果指定IP地址为INADDR_ANY,套接字端可以被绑定在任意一系统网络接口上。

5. 地址转换函数

在IPv4中,通常以点分十进制来表示IP地址,以下函数表示可以在字符串和整型之间的转换。


注:其中前三个函数表示字符串转整型函数;后三个函数表示整型转字符串(点分十进制)函数。

6. 在介绍完一些函数的基本功能和套接字的基本概念后,我们来实现UDP:

UDP服务器端

创建套接字并绑定
  1 #include <stdio.h>                                                          
  2 #include <sys/types.h>
  3 #include <sys/socket.h>
  4 #include <arpa/inet.h>
  5 #include <netinet/in.h>
  6 #include <stdlib.h>
  7 #include <string.h>
  8 
  9 int my_socket(char* IP,int port)
 10 {
 11     int sock=socket(AF_INET,SOCK_DGRAM,0);
 12     if(sock<0){
 13         printf("perror socket!\n");
 14         exit(1);
 15     }
 16     struct sockaddr_in local;
 17     local.sin_family=AF_INET;
 18     local.sin_port=htons(port);
 19     local.sin_addr.s_addr=inet_addr(IP);
 20 
 21     int binding=bind(sock,(struct sockaddr*)&local,sizeof(local));
 22     if(binding<0)
 23     {
 24         printf("perror bind\n");
 25         exit(2);
 26     }
 27     return sock;
 28 }
收发数据
 29 void revandsenddata(int sock)
 30 {
 31     char buffer[521];
 32     struct sockaddr_in client;
 33     while(1)
 34     {
 35         socklen_t len =sizeof(client);
 36         ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,\
 37                 (struct sockaddr*)&client,&len);
 38         if(s>0)
 39         {
 40             buffer[s]=0;
 41             printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),\
 42                     ntohs(client.sin_port),buffer);
 43             sendto(sock,buffer,strlen(buffer),0,\
 44                     (struct sockaddr*)&client,sizeof(client));
 45                 
 46         }
 47     }
 48 }
 50 void help()
 51 {
 52     printf("please enter IP and port\n");
 53 }
 54 int main(int argc,char* argv[])
 55 {
 56    printf("serve:\n");
 57     if(argc!=3)
 58     {
 59         help();
 60         exit(3);
 61     }
 62     char *IP = argv[1];
 63     char *port=argv[2];
 64 
 65    int sock=my_socket(IP,atoi(port));
 66    printf("%d\n" ,sock);
 67    revandsenddata(sock);   close(sock);
 68     return 0;
 69 }                 

UDP客户端

 1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <sys/types.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 int mysockaddr(char* IP)                                                    
  8 {
  9     int sock=socket(AF_INET,SOCK_DGRAM,0);
 10     if(sock<0)
 11     {
 12         printf("perror sockaddr!\n");
 13         exit(1);
 14     }
 15 
 16     struct sockaddr_in serve;
 17     serve.sin_family=AF_INET;
 18     serve.sin_port=htons(8080);
 19     serve.sin_addr.s_addr=inet_addr(IP);
 20 
 21     char buffer[521];
 22     struct sockaddr_in peer;
 23     while(1)
24     {
 25         socklen_t len=sizeof(peer);
 26         printf("please enter:\n");
 27         fflush(stdout);
 28 
 29         ssize_t s=read(0,buffer,sizeof(buffer)-1);
 30 
 31         if(s>0)
 32         {
 33             buffer[s-1]=0;
 34             sendto(sock,buffer,strlen(buffer),0,\
 35                     (struct sockaddr*)&serve,sizeof(serve));
 36             ssize_t ss=recvfrom(sock,buffer,sizeof(buffer)-1,0,\
 37                     (struct sockaddr*)&peer,&len);
 38             if(ss>0)
 39             {
 40                 buffer[s-1]=0;                                              
 41                 printf("serve echo %s\n",buffer);
 42             }
 43         }
 44     }
 45 }
46 void help()
 47 {
 48     printf("please enter IP\n");
 49 }
 50 
 51 int main(int argc,char* argv[])
 52 {
 53     printf("client:\n");
 54     if(argc!=2)
 55     {
 56         help();
 57         exit(2);
 58     }
 59 
 60     int sock=mysockaddr(argv[1]);
        close(sock);
 61     return 0;
 62 }           
7. 实现TCP

TCP(传输控制协议)是一种有连接,可靠的,面向连接,面向字节流的传输协议。

TCP的实现与UDP的实现类似,不过因为它是有连接的,可靠的,所以我们需要在服务器端监听和接收连接,在客户端需建立连接。

listen函数实现监听


参数sockfd表示创建socket后返回的套接字描述符;

重点说一下backlog,backlog表示等待队列长度,指示系统进程所要入队的未完成的连接的请求的数量,具体的值,取决于系统的实现。一旦队列满,系统就会拒绝其他的请求。

实际上,backlog 的值依赖于服务器负载和处理量。一方面,为了让CPU充分发挥它的作用,backlog是必须要有的,另一方面,服务器和CPU的能力有限,即backlog是不能太大的,在TCP中,默认值为128.

accept函数获得连接并建立连接

一旦服务器调用了listen函数,所用的套接字就能接收连接请求。

#include <stdio.h>
int accept (int sockfd,struct sockaddr* addr,socklen_t * len);
返回值:若成功,返回0,失败返回-1;

函数accept的返回值是套接字描述符,该描述符用来和客户端建立connect,然后通过返回的套接字描述符向客户端提供服务。

这个新的套接字描述符和原套接字描述符的地址族相同,但是实现的功能不同,新的主要提供服务,原的主要响应其他连接请求。

connect函数客户端建立连接

如果要处理面向连接的网络服务(SOCK_STREAM和SOCK_SEQPACKET),就需要在客户端调用connect函数和服务器端建立连接。

#include <sys/socket.h>
int connet(int sockfd,const struct sockaddr* addr,socklen_t len);

8. 实现TCP

TCP服务器端

(1).创建socket

 1 #include <stdio.h>                                                          
  2 #include <string.h>
  3 #include <stdlib.h>
  4 #include <netinet/in.h>
  5 #include <errno.h>
  6 #include <unistd.h>
  7 #include <sys/types.h>
  8 #include <sys/socket.h>
  9 
 10 #define _BACKLOG 10
 11 
 12 int mysocket(char* IP,int port)
 13 {
 14     int listen_sock=socket(AF_INET,SOCK_STREAM,0);
 15     if(listen_sock<0)
 16     {
 17         printf("perror socket\n");
 18         exit(2);
 19     }
 20     struct sockaddr_in local;
 21     local.sin_family=AF_INET;
 22     local.sin_port=htons(port);
 23     local.sin_addr.s_addr=inet_addr(IP);
  24     int binding=bind(listen_sock,(struct sockaddr*)&local,\
 25             sizeof(struct sockaddr_in));
 26     if(binding<0)
 27     {
 28         printf("perror bind\n");
 29         exit(3);
 30     }
 31 
 32     if(listen(listen_sock,_BACKLOG)<0)
 33     {
 34         printf("perror listen\n");
 35         close(listen_sock);
 36         exit(4);
 37     }
 38 
 39     return listen_sock;
 40 }
                                               

(2)服务函数

41 void service(int new_sock)
 42 {
 43         char buf[1024];
 44         memset(buf,0,sizeof(buf));
 45     while(1)
 46     {
 47         ssize_t s=read(new_sock,buf,sizeof(buf));
 48         if(s<0)
 49         {
 50             printf("perror read\n");
 51             exit(5);
 52         }
 53         else if(s==0)
 54         {
 55             close(new_sock); 
 56             break;
 57         }
 58         buf[s]=0;
 59         printf("Client: %s\n",buf);
 60         write(new_sock,buf,strlen(buf));
 61     }
 62 }

(3)连接函数

 68 int main(int argc,char*argv[])
 69 {
 70     if(argc!=3)
 71     {
 72         printf("Usage:[IP],[port]!\n");
 73         exit(1);
 74     }
 75 
 76     char* IP=argv[1];
 77     int port=atoi(argv[2]); 
 78     int listen_sock=mysocket(IP,port);
 79     printf("server:%d\n",listen_sock);
 80 
 81     struct sockaddr_in peer;
 82     socklen_t len=sizeof(peer);
 83     for(;;)
 84     {
 85         int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
 86         if(new_sock<0)
 87         {
 88             printf("perror accept\n");
 89             continue;
 91         char bufip[32];
 92         bufip[0]=0;
 93         inet_ntop(AF_INET,&peer.sin_addr,bufip,sizeof(bufip));
 94         printf("get connect,ip is:%s port is:%d\n",\
 95                 bufip,ntohs(peer.sin_port));
 96         service(new_sock);
 97     }
 98 
 99     close(listen_sock);
100 
101     return 0;
102 }               

TCP客户端

  1 #include <stdio.h>                                                          
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 #include <netinet/in.h>
  7 #include <arpa/inet.h>
  8 int main(int argc,char*argv[])
  9 {
 10     if(argc!=2)
 11     {
 12         printf("usage:[IP]\n");
 13         return 1;
 14     }
 15     char *IP=argv[1];
 16     int sock=socket(AF_INET,SOCK_STREAM,0);
 17     if(sock<0)
 18     {
 19         printf("perror sock!\n");
 20         return 2;
 21     }
 22 
 23     struct sockaddr_in local;
 24     local.sin_family=AF_INET;
 25     local.sin_port=htons(8080);
 26     local.sin_addr.s_addr=inet_addr(IP);
 27   //  inet_pton(AF_INET,IP,&local.sin_addr);
 28     int ret=connect(sock,(const struct sockaddr*)&local,sizeof(local));
 29 
 30     if(ret<0){
 31     
 32      printf("perror connect!\n");
 33      return 3;
 34      }
 35      
 36     printf("connect success!\n");
 37 
 38     char buf[1024];
 39     while(1)
 40     {
 41         memset(buf,0,sizeof(buf));
 42         printf("client:please enter!\n");
 43         fflush(stdout);
 44         ssize_t s=read(0,buf,sizeof(buf));
 45         buf[s-1]=0;
 46         if(strcmp(buf,"quit")==0)     
  47         {
 48             printf("client quit!\n");
 49             break;
 50         }
 51         write(sock,buf,sizeof(buf));
 52         s=read(sock,buf,sizeof(buf));
 53         if(s>0)
 54         {
 55             buf[s]=0;
 56             printf("server:%s\n",buf);
 57         }
 58           
 59     }
 60     close(sock);
 61     return 0;
 62 }                                                        



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值