Linux C++:網絡編程

   以前學C語言時學過,現在學C++時可以複習,重新鞏固下基礎知識。

什麼是套接字:

    TCP用主機的IP地址加上主機上的端口號作爲TCP連接的端點,這種端點就叫做套接字(socket)或插口。套接字用(IP地址:端口號)表示。

套接字分類:

  1. 流式套接字(SOCK_STREAM):流式套接字用於提供面向連接、可靠的數據傳輸服務。該服務將保證數據能夠實現無差錯、無重複發送,並按順序接收。流式套接字之所以能夠實現可靠的數據服務,原因在於其使用了傳輸控制協議,即TCP(The Transmission Control Protocol)協議。
  2. 數據報套接字(SOCK_DGRAM):數據報套接字提供了一種無連接的服務。該服務並不能保證數據傳輸的可靠性,數據有可能在傳輸過程中丟失或出現數據重複,且無法保證順序地接收到數據。數據報套接字使用UDP(User Datagram Protocol)協議進行數據的傳輸。由於數據報套接字不能保證數據傳輸的可靠性,對於有可能出現的數據丟失情況,需要在程序中做相應的處理。
  3. 原始套接字(SOCK_RAW):原始套接字(SOCKET_RAW)允許對較低層次的協議直接訪問,比如IP、 ICMP協議,它常用於檢驗新的協議實現,或者訪問現有服務中配置的新設備,因爲RAW SOCKET可以自如地控制Windows下的多種協議,能夠對網絡底層的傳輸機制進行控制,所以可以應用原始套接字來操縱網絡層和傳輸層應用。比如,我們可以通過RAW SOCKET來接收發向本機的ICMP、IGMP協議包,或者接收TCP/IP棧不能夠處理的IP包,也可以用來發送一些自定包頭或自定協議的IP包。網絡監聽技術很大程度上依賴於SOCKET_RAW。

網絡通信一般使用CS模式連接。

網絡模型架構:

TCP/UDP屬於傳輸層。

TCP網絡通信:

    TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。在簡化的計算機網絡OSI模型中,它完成第四層傳輸層所指定的功能,用戶數據報協議(UDP)是同一層內 [1]  另一個重要的傳輸協議。在因特網協議族(Internet protocol suite)中,TCP層是位於IP層之上,應用層之下的中間層。不同主機的應用層之間經常需要可靠的、像管道一樣的連接,但是IP層不提供這樣的流機制,而是提供不可靠的包交換。

通信流程圖如下

TCP三次握手建立連接:

  • 第一次握手:客戶端發送syn包(seq=x)到服務器,並進入SYN_SEND狀態,等待服務器確認;
  • 第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時自己也發送一個SYN包(seq=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
  • 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

TCP四次握手斷開連接:

  • 第一次握手:主動關閉方發送一個FIN,用來關閉主動方到被動關閉方的數據傳送,也就是主動關閉方告訴被動關閉方:我已經不會再給你發數據了(當 然,在fin包之前發送出去的數據,如果沒有收到對應的ack確認報文,主動關閉方依然會重發這些數據),但此時主動關閉方還可以接受數據。
  • 第二次握手:被動關閉方收到FIN包後,發送一個ACK給對方,確認序號爲收到序號+1(與SYN相同,一個FIN佔用一個序號)。
  • 第三次握手:被動關閉方發送一個FIN,用來關閉被動關閉方到主動關閉方的數據傳送,也就是告訴主動關閉方,我的數據也發送完了,不會再給你發數據了。
  • 第四次握手:主動關閉方收到FIN後,發送一個ACK給被動關閉方,確認序號爲收到序號+1,至此,完成四次揮手。

代碼示例服務端(使用了select  模式,目前只可以進行一對一連接,如果想一對多的話,添加多線程即可)

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>

#define SERVER_PORT 6555

int main() {
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd;     //選擇器


    /*創建socket*/
    int ss = socket(AF_INET, SOCK_STREAM, 0);   //AF_INET   IPV4   ;SOCK_STREAM   TCP
    struct sockaddr_in server_sockaddr;
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(SERVER_PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    /*bind*/
    if(bind(ss, (struct sockaddr* ) &server_sockaddr, sizeof(server_sockaddr))==-1) {
        perror("bind");
        exit(1);
    }
    /*listen*/
    if(listen(ss, 5) == -1) {
        perror("listen");
        exit(1);
    }
    /*connect*/
    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);
    ///成功返回非負描述字,出錯返回-1
    int conn = accept(ss, (struct sockaddr*)&client_addr, &length);   //目測需要客戶端部分的addr
    if( conn < 0 ) {
        perror("connect");
        exit(1);
    }
    while(1) {
        /*把可讀文件描述符的集合清空*/
        FD_ZERO(&rfds);
        /*把標準輸入的文件描述符加入到集合中*/
        FD_SET(0, &rfds);
        maxfd = 0;
        /*把當前連接的文件描述符加入到集合中*/
        FD_SET(conn, &rfds);
        /*找出文件描述符集合中最大的文件描述符*/
        if(maxfd < conn)
            maxfd = conn;
        /*設置超時時間*/
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        /*等待聊天*/
        retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
        if(retval == -1){
            printf("select出錯,客戶端程序退出\n");
            break;
        }else if(retval == 0){
            printf("服務端沒有任何輸入信息,並且客戶端也沒有信息到來,waiting...\n");
            continue;
        }else{
            /*客戶端發來了消息*/
            if(FD_ISSET(conn,&rfds)){
                char buffer[1024];
                memset(buffer, 0 ,sizeof(buffer));
                int len = recv(conn, buffer, sizeof(buffer), 0);
                if(strcmp(buffer, "exit\n") == 0) break;
                printf("%s", buffer);
                //send(conn, buffer, len , 0);把數據回發給客戶端
            }
            /*用戶輸入信息了,開始處理信息併發送*/
            if(FD_ISSET(0, &rfds)){
                char buf[1024];
                fgets(buf, sizeof(buf), stdin);
                //printf("you are send %s", buf);
                send(conn, buf, sizeof(buf), 0);
            }
        }
    }
    close(conn);
    close(ss);
    return 0;
}

客戶端:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT  6555
#define BUFFER_SIZE 1024
int main()
{
    int sock_cli;
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd;

    ///定義sockfd
    sock_cli = socket(AF_INET,SOCK_STREAM, 0);
    ///定義sockaddr_in
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(MYPORT);  ///服務器端口
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");  ///服務器ip

    //連接服務器,成功返回0,錯誤返回-1
    while (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect");
        exit(1);
    }

    while(1){
        /*把可讀文件描述符的集合清空*/
        FD_ZERO(&rfds);
        /*把標準輸入的文件描述符加入到集合中*/
        FD_SET(0, &rfds);
        maxfd = 0;
        /*把當前連接的文件描述符加入到集合中*/
        FD_SET(sock_cli, &rfds);
        /*找出文件描述符集合中最大的文件描述符*/
        if(maxfd < sock_cli)
            maxfd = sock_cli;
        /*設置超時時間*/
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        /*等待聊天*/
        retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
        if(retval == -1){
            printf("select出錯,客戶端程序退出\n");
            break;
        }else if(retval == 0){
            printf("客戶端沒有任何輸入信息,並且服務器也沒有信息到來,waiting...\n");
            continue;
        }else{
            /*服務器發來了消息*/
            if(FD_ISSET(sock_cli,&rfds)){
                char recvbuf[BUFFER_SIZE];
                int len;
                len = recv(sock_cli, recvbuf, sizeof(recvbuf),0);
                printf("%s", recvbuf);
                memset(recvbuf, 0, sizeof(recvbuf));
            }
            /*用戶輸入信息了,開始處理信息併發送*/
            if(FD_ISSET(0, &rfds)){
                char sendbuf[BUFFER_SIZE];
                fgets(sendbuf, sizeof(sendbuf), stdin);
                send(sock_cli, sendbuf, strlen(sendbuf),0); //發送
                memset(sendbuf, 0, sizeof(sendbuf));
            }
        }
    }

    close(sock_cli);
    return 0;
}

UDP網絡通信:

    UDP 是User Datagram Protocol的簡稱, 中文名是用戶數據報協議,是OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務,IETF RFC 768是UDP的正式規範。UDP在IP報文的協議號是17。使用UDP可進行 單播 、廣播 、組播。

UDP網絡通信流程圖:

示例代碼服務端

#include <stdio.h>  
#include <iostream> 
#include <sys/types.h>   
#include <sys/socket.h>   
#include <netinet/in.h>   
#include <unistd.h>   
#include <errno.h>   
#include <string.h>   
#include <stdlib.h>   
  
#define SERV_PORT   8000   
  
int main()  
{  
  /* sock_fd --- socket文件描述符 創建udp套接字*/  
  int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if(sock_fd < 0)  
  {  
    perror("socket");  
    exit(1);  
  }  
  
  /* 將套接字和IP、端口綁定 */  
  struct sockaddr_in addr_serv;  
  int len;  
  memset(&addr_serv, 0, sizeof(struct sockaddr_in));  //每個字節都用0填充
  addr_serv.sin_family = AF_INET;//使用IPV4地址
  addr_serv.sin_port = htons(SERV_PORT);//端口
  /* INADDR_ANY表示不管是哪個網卡接收到數據,只要目的端口是SERV_PORT,就會被該應用程序接收到 */  
  addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);  //自動獲取IP地址
  len = sizeof(addr_serv);  
    
  /* 綁定socket */  
  if(bind(sock_fd, (struct sockaddr *)&addr_serv, sizeof(addr_serv)) < 0)  
  {  
    perror("bind error:");  
    exit(1);  
  }  
  
    
  int recv_num;  
  int send_num;  
  char send_buf[20] = "i am server!";  
  char recv_buf[20];  
  struct sockaddr_in addr_client;  
  
  while(1)  
  {  
    printf("server wait:\n");  
      
    recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_client, (socklen_t *)&len);  
      
    if(recv_num < 0)  
    {  
      perror("recvfrom error:");  
      exit(1);  
    }  
  
    recv_buf[recv_num] = '\0';  
    printf("server receive %d bytes: %s\n", recv_num, recv_buf);  
  
    send_num = sendto(sock_fd, send_buf, recv_num, 0, (struct sockaddr *)&addr_client, len);  
      
    if(send_num < 0)  
    {  
      perror("sendto error:");  
      exit(1);  
    }  
  }  
    
  close(sock_fd);  
    
  return 0;  
}

客戶端

#include <stdio.h>   
#include <string.h>   
#include <errno.h>   
#include <stdlib.h>   
#include <unistd.h>   
#include <sys/types.h>   
#include <sys/socket.h>   
#include <netinet/in.h>   
#include <arpa/inet.h>   
#include <iostream>   
  
#define DEST_PORT 8000   
#define DSET_IP_ADDRESS  "127.0.0.1"   
   
  
int main()  
{  
  /* socket文件描述符 */  
  int sock_fd;  
  
  /* 建立udp socket */  
  sock_fd = socket(AF_INET, SOCK_DGRAM, 0);  
  if(sock_fd < 0)  
  {  
    perror("socket");  
    exit(1);  
  }  
    
  /* 設置address */  
  struct sockaddr_in addr_serv;  
  int len;  
  memset(&addr_serv, 0, sizeof(addr_serv));  
  addr_serv.sin_family = AF_INET;  
  addr_serv.sin_addr.s_addr = inet_addr(DSET_IP_ADDRESS);  
  addr_serv.sin_port = htons(DEST_PORT);  
  len = sizeof(addr_serv);  
  
    
  int send_num;  
  int recv_num;  
  char send_buf[20] = "hey, who are you?";  
  char recv_buf[20];  
      
  printf("client send: %s\n", send_buf);  
    
  send_num = sendto(sock_fd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&addr_serv, len);  
    
  if(send_num < 0)  
  {  
    perror("sendto error:");  
    exit(1);  
  }  
    
  recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_serv, (socklen_t *)&len);  
    
  if(recv_num < 0)  
  {  
    perror("recvfrom error:");  
    exit(1);  
  }  
    
  recv_buf[recv_num] = '\0';  
  printf("client receive %d bytes: %s\n", recv_num, recv_buf);  
    
  close(sock_fd);  
    
  return 0;  
}

關於UDP廣播組播相關示例待續...

 

參考:

http://www.cnblogs.com/zkfopen/p/9382705.html

https://www.cnblogs.com/wuyepeng/p/9737583.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章