• Linux系统编程:socket网络编程(操作篇)


    一、问题思考

    问1.网络通信应用在什么场合?通信的前提是什么?

    答1.主要应用在不同主机进程间的互相通信,同一主机的进程也可以使用网络进行通信。通信的前提是如何标识通信进程的唯一,由于不同主机的进程极有可能具有相同的PID,因此,在网络中单单靠PID是无法准确进行标识进程身份的,TCP/IP协议族网络层的IP地址可以唯一的标识连入网络的主机。

    问2.socket编程重点是什么?

    答2.掌握基于TCP、UDP的S/C架构的编程要点;掌握网络通信方式之间的区别和应用场合。

    问3.什么是网络模型?

    答3.网络模型主要分为4层,从上至下依次为:应用层、运输层、协议层、链路层。如图所示。

       

    问4.什么是socket套接字?

    答4.是一种文件描述符。有三种类型:

    ①流式套接字(SOCK_STREAM)——过程类似于打电话。提供可靠的、面向连接的通信流,对应使用TCP/IP协议,能够保证数据传输的正确性和顺序性。

    ②数据报套接字(SOCK_DGRAM)——过程类似于手机之间发短信。无连接服务、数据的传输是独立的不需要经过对方的响应,可靠性无保证。对应使用UDP/IP协议。

    ③原始套接字(SOCK_RAW)——该套接字直接基于IP地址。

     二、TCP、UDP通信编程模型

     

    主要相关操作函数

     1. 创建套接字

    #include<sys/types.h>
    #include<sys/socket.h>
    int socket(int protofamil,int type,int protocol);
    
    protofamil 设置协议族即设置socket的地址类型,定义在/usr/include/bits/socket.h 内,有:AF_INET(IPV4——32位IP、16位的端口号)、AF_INET6(IPV6)、AF_LOCAL(Unix域socket)、AF_ROUTE等等;
    
    type 常用的有SOCK_STREAM、SOCK_DGRAM和SOCK_RAW;
    
    protocol 用来指定socket所使用的传输协议编号,通常此参考不用管它,设为0即可。
    参数含义

    返回值:成功——socket描述符;失败——(-1)

    2.绑定地址

    #include<sys/types.h>
    #include<sys/socket.h>
    int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
    
    sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
    my_addr:指向sockfd要绑定的协议地址。IPv4对应的协议地址结构为:
    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 */
    };
    addrlen:对应的是地址的长度,使用sizeof来计算。
    参数含义

     返回值:成功——(0),失败——(-1)

    3.监听

    #include<sys/socket.h>
    int listen(int sockfd,int backlog);
    

    参数backlog是能处理的最大连接数目,如果连接数目达此上限则client端将出错。

    返回值:成功——(0),失败——(-1)

    4.建立连接

    #include<sys/types.h>
    #include<sys/socket.h>
    int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
    

    参数:sockaddr 服务器的socket地址,addrlen为socket地址的长度。

    返回值:成功——(0),失败——(-1)

    5.接受请求

    #include<sys/types.h>
    #include<sys/socket.h>
    int accept(int sockfd,struct sockaddr * addr,int * addrlen);
    

    参数:sockfd ——用来监听客户的一个端口号;addr——客户机的地址数据;addrlen即前面这个地址数据的大小

    返回值:成功——已建立连接的新的套接字描述符,可用于read/write函数;失败——(-1)

    6.读写操作

    第①组:用于面向连接的TCP编程中

    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
    ssize_t write(int fd, const void *buf, size_t count);
    

    返回值:成功——字节数;失败——(-1)

    第②组:用于面向连接的TCP编程中

    #include<sys/types.h>
    #include<sys/socket.h>
    int recv(int sockfd,void *buf,int len,unsigned int flags);
    int send(int sockfd,const void * msg,int len,unsigned int falgs);
    

    参数:buf/msg分别指向接收缓冲区和发送的信息,len是接收或发送的最大长度,flags通常设置为0。

    返回值:成功——字节数;失败——(-1)

    第③组:可用于面向连接和无连接的套接字

    #include<sys/types.h>
    #include<sys/socket.h>
    
    int recvfrom(int sockfd,void *buf,int len,unsigned int flags ,struct sockaddr *from ,int *fromlen);
    int sendto ( int sockfd, const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen ) ;
    
    int recvmsg(int sockfd,struct msghdr *msg,unsigned int flags);
    int sendmsg(int sockfd,const strcut msghdr *msg,unsigned int flags);
    

    返回值:成功——字节数;失败——(-1)

    7.关闭操作

    #include<unistd.h>
    int close(int fd);
    

    8.其他:申请共享内存

    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg);
    函数功能:得到一个共享内存标识符
    参数含义:
    key:常设置为IPC_PRIVATE(0),建立新共享内存对象,大于0的32位整数:视参数shmflg来确定操作。
    size:新建的共享内存大小,以字节为单位
    shmflg:根据如下进行设置
    0:取共享内存标识符,若不存在则函数会报错;
    IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符;
    IPC_CREAT|IPC_EXCL:如果内核中不存在键值 与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错;
    共享内存的存取权限:S_IRUSR|S_IWUSR...
    shmget
    #include <sys/types.h>
    #include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    函数功能:映射共享内存区到调用该函数的进程地址空间
    参数含义:
    shmid:共享内存标识符,由shmget函数产生
    shmaddr:通常指定为NULL(0)让内核自己决定一个合适的地址位置
    shmflg:如果设置为SHM_RDONLY-只读模式,其他值为读写模式
    成功:返回共享内存地址
    shmat
    #include <sys/types.h>
    #include <sys/shm.h>
    int shmdt(const void *shmaddr);
    函数功能:断开共享内存连接
    参数分析:
    shmaddr:要断开共享内存的起始地址
    shmdt

    三、socket编程框架与示例

    1.TCP网络编程设计:TCP并发服务器设计——每一个客户机的请求由服务器创建的一个子进程/线程来处理,不是用服务器进程来处理(TCP循环服务器:一次只能处理一个客户机额请求)。优点:可以“同时”响应多个客户端的服务请求。

    代码功能实现:客户端A和B通过服务端进行通信

      1 //编译指令:gcc -o server server.c
      2 //执行指令:./server
      3 #include<stdio.h>    
      4 #include<stdlib.h>  
      5 #include<sys/types.h>  //数据类型定义 
      6 #include<sys/stat.h>  
      7 #include<netinet/in.h>  //定义数据结构sockaddr_in 
      8 #include<sys/socket.h>  //提供socket函数及数据结构 
      9 #include<string.h> 
     10 #include<unistd.h> 
     11 #include<signal.h> 
     12 #include<sys/ipc.h> 
     13 #include<errno.h> 
     14 #include<sys/shm.h> 
     15 #include<time.h>  
     16 #define PERM S_IRUSR|S_IWUSR    
     17 #define MYPORT      5000     //宏定义定义通信端口  
     18 #define MAX_CLIENT  5        //宏定义,定义服务程序可以连接的最大客户数量  
     19 #define WELCOME "chating room:"  //宏定义,当客户端连接服务端时,想客户发送此欢迎字符串 
     20 
     21 //转换函数,将int类型转换成char *类型 
     22 static void itoa(int i,char*string) 
     23 {     
     24     int power,j;    
     25     j=i;     
     26     for(power=1;j>=10;j/=10)      
     27     power*=10;     
     28     for(;power>0;power/=10)    
     29     {       
     30         *string++='0'+i/power; 
     31          i%=power;    
     32     }     
     33     *string=''; 
     34 }
     35 
     36 //得到当前系统时间,并将时间转换成字符串形式存放time_str指向处。
     37 void get_cur_time(char * time_str) 
     38 {     
     39     time_t timep;     
     40     struct tm *p_curtime;    
     41     char *time_tmp;     
     42     time_tmp=(char *)malloc(2);    
     43     memset(time_tmp,0,2);      
     44     memset(time_str,0,20);    
     45     time(&timep);     
     46     p_curtime = localtime(&timep);    
     47     strcat(time_str," (");     
     48     itoa(p_curtime->tm_hour,time_tmp);    
     49     strcat(time_str,time_tmp);    
     50     strcat(time_str,":");
     51     itoa(p_curtime->tm_min,time_tmp);
     52     strcat(time_str,time_tmp);    
     53     strcat(time_str,":");     
     54     itoa(p_curtime->tm_sec,time_tmp);    
     55     strcat(time_str,time_tmp);    
     56     strcat(time_str,")");    
     57     free(time_tmp);
     58 }
     59 
     60 //创建共享存储区 
     61 key_t shm_create()
     62 {
     63     key_t shmid;  //key_t实际上是int型
     64     if((shmid = shmget(IPC_PRIVATE,1024,PERM)) == -1)    
     65     {       
     66         fprintf(stderr,"Create Share Memory Error:%s
    a",strerror(errno));      
     67         exit(1);
     68     }
     69     return shmid; //返回共享内存的标识符
     70 }
     71 
     72 //端口绑定函数:创建套接字,并绑定到指定端口 
     73 int bindPort(unsigned short int port) 
     74 { 
     75     int sockfd;     
     76     struct sockaddr_in my_addr;     
     77     sockfd = socket(AF_INET,SOCK_STREAM,0);//创建基于流套接字    
     78     my_addr.sin_family = AF_INET;//IPv4协议族    
     79     my_addr.sin_port = htons(port);//端口转换    
     80     my_addr.sin_addr.s_addr = INADDR_ANY;    
     81     bzero(&(my_addr.sin_zero),0);      
     82     if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr)) == -1)    
     83     {       
     84         perror("bind");      
     85         exit(1);    
     86     }     
     87     printf("bing success!
    ");    
     88     return sockfd; 
     89 } 
     90 
     91 int main(int argc, char *argv[]) 
     92 {     
     93     int sockfd,clientfd,sin_size,recvbytes; //定义监听套接字、客户套接字    
     94     pid_t pid,ppid;
     95     char *buf, *r_addr, *w_addr, *temp, *time_str;//=""; //定义临时存储区    
     96     struct sockaddr_in their_addr;  //定义地址结构    
     97     key_t shmid;
     98     shmid = shm_create(); //创建共享存储区      
     99     temp = (char *)malloc(255);    
    100     time_str=(char *)malloc(20);
    101     
    102     sockfd = bindPort(MYPORT);//绑定端口
    103     
    104     while(1)    
    105     {            
    106         if(listen(sockfd,MAX_CLIENT) == -1)//在指定端口上监听      
    107         {         
    108             perror("listen");        
    109             exit(1);      
    110         }
    111         printf("listening......
    ");       
    112         if((clientfd = accept(sockfd,(struct sockaddr*)&their_addr,&sin_size)) == -1)//接收客户端连接      
    113         {         
    114             perror("accept");        
    115             exit(1);
    116         }       
    117         printf("accept from:%d
    ",inet_ntoa(their_addr.sin_addr)); 
    118         send(clientfd,WELCOME,strlen(WELCOME),0);//发送问候信息      
    119         buf = (char *)malloc(255);            
    120         ppid = fork();     //创建子进程
    121         if(ppid == 0)      
    122         {                
    123             pid = fork();  //创建子进程 -- TCP并发服务器       
    124             while(1)        
    125             {           
    126                 if(pid > 0)          
    127                 {               
    128                     //父进程:接收信息            
    129                     memset(buf,0,255);                      
    130                     if((recvbytes = recv(clientfd,buf,255,0)) <= 0)            
    131                     {               
    132                         perror("recv1");              
    133                         close(clientfd);              
    134                         raise(SIGKILL);              
    135                         exit(1);            
    136                     }             
    137                     //write buf's data to share memory            
    138                     w_addr = shmat(shmid, 0, 0);                 
    139                     memset(w_addr, '', 1024);            
    140                     strncpy(w_addr, buf, 1024);            
    141                     get_cur_time(time_str);            
    142                     strcat(buf,time_str);            
    143                     printf(" %s
    ",buf);          
    144                 }           
    145                 else if(pid == 0)          
    146                 {              
    147                     //子进程用于发送信息                     
    148                     sleep(1);             
    149                     r_addr = shmat(shmid, 0, 0);
    150                     if(strcmp(temp,r_addr) != 0)            
    151                     {               
    152                         strcpy(temp,r_addr);
    153                         get_cur_time(time_str);
    154                         strcat(r_addr,time_str);              
    155                         if(send(clientfd,r_addr,strlen(r_addr),0) == -1)              
    156                         {                 
    157                             perror("send");              
    158                         }               
    159                         memset(r_addr, '', 1024);              
    160                         strcpy(r_addr,temp);            
    161                     }
    162                 }          
    163                 else             
    164                     perror("fork");        
    165             }      
    166         }  
    167     } 
    168     printf("------------------------------
    ");    
    169     free(buf);    
    170     close(sockfd);    
    171     close(clientfd);    
    172     return 0; 
    173 }
    server.c
     1 //编译指令:gcc -o client client.c
     2 //执行指令:./client 192.168.1.107 5000 user_name
     3 #include<stdio.h>  
     4 #include<netinet/in.h>  //定义数据结构sockaddr_in 
     5 #include<sys/socket.h>  //提供socket函数及数据结构 
     6 #include<sys/types.h>   //数据类型定义 
     7 #include<string.h> 
     8 #include<stdlib.h> 
     9 #include<netdb.h> 
    10 #include<unistd.h> 
    11 #include<signal.h> 
    12 #include<time.h>
    13 
    14 int main(int argc, char *argv[]) 
    15 {     
    16     struct sockaddr_in clientaddr;//定义地址结构    
    17     pid_t pid;     
    18     int clientfd,sendbytes,recvbytes;//定义客户端套接字    
    19     struct hostent *host;    
    20     char *buf,*buf_r;     
    21     if(argc < 4)    
    22     {      
    23         printf("usage:
    ");
    24         printf("%s host port name
    ",argv[0]);     
    25         exit(1);    
    26     }     
    27     host = gethostbyname(argv[1]);     
    28     if((clientfd = socket(AF_INET,SOCK_STREAM,0)) == -1)  //创建客户端套接字    
    29     {       
    30         perror("socket
    ");      
    31         exit(1);    
    32     }    
    33     //绑定客户端套接字     
    34     clientaddr.sin_family = AF_INET;     
    35     clientaddr.sin_port = htons((uint16_t)atoi(argv[2]));
    36     clientaddr.sin_addr = *((struct in_addr *)host->h_addr);    
    37     bzero(&(clientaddr.sin_zero),0);     
    38     if(connect(clientfd,(struct sockaddr *)&clientaddr,sizeof(struct sockaddr)) == -1) //连接服务端    
    39     {       
    40         perror("connect error.
    ");      
    41         exit(1);
    42     }     
    43     buf=(char *)malloc(120);  
    44     memset(buf,0,120);
    45     buf_r=(char *)malloc(100);      
    46     if( recv(clientfd,buf,100,0) == -1)    
    47     {       
    48         perror("recv:");      
    49         exit(1);    
    50     }     
    51     printf("
    %s
    ",buf);      
    52     pid = fork();//创建子进程    
    53     while(1)    
    54     {       
    55         if(pid > 0)
    56         {           
    57             //父进程:发送信息                
    58             strcpy(buf,argv[3]);
    59             strcat(buf,":");
    60             memset(buf_r,0,100);    
    61             fgets(buf_r,100,stdin); //从标准输入获取
    62             strncat(buf,buf_r,strlen(buf_r)-1);  //连接两个字符串         
    63             if((sendbytes = send(clientfd,buf,strlen(buf),0)) == -1)        
    64             {           
    65                 perror("send
    ");          
    66                 exit(1);        
    67             } 
    68         } 
    69         else if(pid == 0)      
    70         {    
    71             //子进程:接收信息        
    72             memset(buf,0,100);        
    73             if(recv(clientfd,buf,100,0) <= 0)        
    74             {           
    75                 perror("recv:");
    76                 close(clientfd);          
    77                 raise(SIGSTOP);          
    78                 exit(1);        
    79             }         
    80             printf("%s
    ",buf);
    81         }      
    82         else         
    83             perror("fork error.");    
    84     }
    85     close(clientfd);    
    86     return 0;
    87 }
    client.c

    提示:运行测试程序时,客户端A/B和服务端分别在一个终端页面进行。

    2.UDP网络编程设计:UDP循环服务器设计——server每一次从socket上获得client的请求,然后进行处理,再把结果返回给client,如此循环。缺点:如果一个客户端一直占有这个服务端,其他的客户端就不能获得服务端的服务。

     1 //编译指令:gcc -o server server.c
     2 //运行指令:./server
     3 #include <stdlib.h>
     4 #include <stdio.h>
     5 #include <errno.h>
     6 #include <string.h>
     7 #include <unistd.h>
     8 #include <netdb.h>
     9 #include <sys/socket.h>
    10 #include <netinet/in.h>
    11 #include <sys/types.h>
    12 #include <arpa/inet.h>
    13 
    14 #define SERVER_PORT  5001
    15 #define MAX_MSG_SIZE 1024
    16 
    17 int main(void) 
    18 { 
    19     int sockfd; 
    20     struct sockaddr_in addr,addr1; 
    21     int addrlen,n; 
    
    22     char msg[MAX_MSG_SIZE]; 
    23 
    24     /* 服务器端建立socket描述符 */ 
    25     sockfd=socket(AF_INET,SOCK_DGRAM,0); 
    26     if(sockfd<0) 
    27     { 
    28         fprintf(stderr,"Socket Error:%s
    ",strerror(errno)); 
    29         exit(1); 
    30     }
    31 
    32     bzero(&addr,sizeof(struct sockaddr_in)); 
    33     addr.sin_family=AF_INET; 
    34     addr.sin_addr.s_addr=htonl(INADDR_ANY); 
    35     addr.sin_port=htons(SERVER_PORT); 
    36 
    37     if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0) 
    38     { 
    39         fprintf(stderr,"Bind Error:%s
    ",strerror(errno)); 
    40         exit(1); 
    41     }
    42     
    43     while(1) 
    44     {
    45         bzero(msg,sizeof(msg));
    46         addrlen = sizeof(struct sockaddr);
    47         n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,(struct sockaddr*)&addr1,&addrlen); // 从客户端接收消息
    48         msg[n]=0;//添加上字符串结束标志 
    49         fprintf(stdout,"Server have received %s",msg); // 显示消息
    50     }
    51     
    52     close(sockfd); 
    53 } 
    server.c
     1 //编译指令:gcc -o client client.c
     2 //运行指令:./client 192.168.1.107
     3 #include <stdlib.h>
     4 #include <stdio.h>
     5 #include <errno.h>
     6 #include <string.h>
     7 #include <unistd.h>
     8 #include <netdb.h>
     9 #include <sys/socket.h>
    10 #include <netinet/in.h>
    11 #include <sys/types.h>
    12 #include <arpa/inet.h>
    13 
    14 #define SERVER_PORT  5001
    15 #define MAX_BUF_SIZE 1024 
    16 
    17 int main(int argc,char *argv[]) 
    18 { 
    19     int sockfd; 
    20     struct sockaddr_in addr; 
    21     char buffer[MAX_BUF_SIZE]; 
    22     int n;
    23     
    24     if(argc!=2) 
    25     { 
    26         fprintf(stderr,"Usage:%s server_ip
    ",argv[0]); 
    27         exit(1); 
    28     }
    29 
    30     /* 建立 sockfd描述符 */ 
    31     sockfd=socket(AF_INET,SOCK_DGRAM,0); 
    32     if(sockfd<0) 
    33     { 
    34         fprintf(stderr,"Socket Error:%s
    ",strerror(errno)); 
    35         exit(1); 
    36     } 
    37 
    38     bzero(&addr,sizeof(struct sockaddr_in)); 
    39     addr.sin_family=AF_INET; 
    40     addr.sin_port=htons(SERVER_PORT);
    41     if(inet_aton(argv[1],&addr.sin_addr)<0)  //inet_aton函数用于把字符串型的IP地址转化成网络二进制数字
    42     { 
    43         fprintf(stderr,"Ip error:%s
    ",strerror(errno)); 
    44         exit(1); 
    45     }
    46 
    47     while(1) 
    48     {     /* 从键盘读入,写到服务端 */ 
    49         printf("Please input char:
    ");
    50         fgets(buffer,MAX_BUF_SIZE,stdin);
    51         sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr *)(&addr),sizeof(struct sockaddr_in)); 
    52         bzero(buffer,MAX_BUF_SIZE);
    53     } 
    54     
    55     close(sockfd); 
    56 } 
    client.c
  • 相关阅读:
    C语言字母频率统计
    C语言文件操作相关函数
    【蓝桥杯】历届试题 回文数字
    【蓝桥杯】历届试题 蚂蚁感冒
    【蓝桥杯】历届试题 地宫取宝
    【蓝桥杯】历届试题 分糖果
    【蓝桥杯】历届试题 兰顿蚂蚁
    JDK的安装和配置
    【蓝桥杯】历届试题 错误票据
    【蓝桥杯】历届试题 带分数
  • 原文地址:https://www.cnblogs.com/lubiao/p/4741613.html
Copyright © 2020-2023  润新知