c++ 網絡編程(一)TCP/UDP windows/linux 下入門級socket通訊 客戶端與服務端交互代碼

 

原文做者:aircrafthtml

原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html前端

 

    c++ 網絡編程(一)TCP/UDP  入門級客戶端與服務端交互代碼python

 

 

網絡編程和套接字

 

  • 網絡編程其實和咱們計算機上的文件讀取操做很相似,通俗地講,網絡編程就是編寫程序使兩臺聯網的計算機相互交換數據。那麼,數據具體怎麼傳輸呢?其實操做系統會提供名爲「套接字」的部件,套接字就是網絡數據傳輸用的軟件設備而已。即便你對網絡數據傳輸原理不太熟悉,你也能夠經過套接字完成數據傳輸。所以,網絡編程經常又稱爲套接字編程。linux

  • 下面咱們再經過一個通俗地例子來理解什麼是套接字並給出建立它的過程。實際上,這個過程相似咱們的電話機系統,電話機經過固定電話網完成語言數據的交換。這裏的電話機就相似咱們的套接字,電網就相似咱們的互聯網。和電話能夠撥打或接聽同樣,套接字也能夠發送或接收。先來看看接收的套接字建立過程:
    1,打電話首先須要準備什麼?固然得是要先有一臺電話機。建立至關於電話機的套接字,以下:ios

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

    2,準備好電話機後要考慮分配電話號碼的問題,這樣別人才能聯繫到你。套接字也同樣,利用下面函數建立好套接字分配地址信息(IP地址和端口號)。程序員

    int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);編程

    3,作了上面兩步後,接下來就是須要鏈接電話線並等待來電。一鏈接電話線,電話機就轉爲了可接聽狀態,這時其餘人能夠撥打電話請求鏈接到該機了。一樣,須要把套接字轉化成可接收鏈接的狀態。windows

    int listen(int sockfd, int backlog);後端

    4,前面都作好後,若是有人撥打電話就會響鈴,拿起話筒才能接聽電話。套接字一樣如此,若是有人爲了完成數據傳輸而請求鏈接,就須要調用下面函數進行受理。

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • 總結下網絡中接收鏈接請求的套接字建立過程以下:
    第一步:調用socket函數建立套接字。
    第二步:調用bind函數分配IP地址和端口號。
    第三部:調用listen函數轉爲可接收請求狀態。
    第四步:調用accept函數受理鏈接請求。

  • 上面講的都是接電話,即服務端套接字(接收),下面咱們再來說講打電話,即客服端套接字(發送)。這個要簡單,只有兩步:1,調用socket函數建立套接字。2,調用connect函數向服務端發送鏈接請求。

    int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);

 

基於Linux的文件操做

 

1,在這裏爲何要討論Linux上的文件操做呢?由於Linux上,socket操做與文件操做沒有區別,在Linux上,socket也被認爲是文件的一種。
注:Linux上的C語言編譯器–GCC,具體使用就不在這裏講了。

 

2,文件描述符:是系統自動分配給文件或套接字的整數。下面咱們再來經過一個例子理解下它:假設學校有個打印室,只須要打個電話就能複印所需論文。有一位同窗,常常打電話要複印這樣個內容:「<<關於隨着高度信息化社會而逐漸提高地位的觸覺,知覺,思惟,性格,智力等人類生活質量相關問題特性的人類學研究>>這篇論文第26頁到30頁」。終於有一天,打印室的人感受這樣太不方便了,因而,打印室的人和那位同窗說:「之後那篇論文就編爲第18號,你就說幫我複印18號論文26頁到30頁」。在該例中,打印室至關於操做系統,那位同窗至關於程序員,論文號至關於文件描述符,論文至關於文件或套接字。也就是說,每當生成文件或套接字,操做系統就會自動返回給咱們一個整數。這個整數就是文件描述符,即建立的文件或套接字的別名,方便稱呼而已。
注:文件描述符在Windows中又稱爲句柄。

 

3,Linux上的文件或套接字操做:
打開文件:

 

int open(const char *path, int flag); –> (Linux上對應socket(…)函數)

 

關閉文件或套接字:

 

int close(int fd); –>(Windows上對應closesocket(SOCKET S)函數)

 

將數據寫入文件或傳遞數據:

 

ssize_t write(int fd, const void *buf, size_t nbytes);

 

讀取文件中數據或接收數據:

 

ssize_t read(int fd, void *buf, size_t nbytes);

 

註釋:ssize_t = signed int, size_t = unsigned int,其實它們都是經過typedef聲明的,爲基本數據類型取的別名而已。既然已經有了基本數據類型,那麼爲何還須要爲它取別名呢?是由於目前廣泛認爲int是32位的,而過去16位操做系統時代,int是16位的。根據系統的不一樣,時代的變化,基本數據類型的表現形式也隨着變化的。若是爲基本數據類型取了別名,之後要修改,也就只須要修改typedef聲明便可,這將大大減小代碼變更。

 

基於Windows平臺的實現

 

1,Windows套接字大部分是參考BSD系列UNIX套接字設計的,因此不少地方都跟Linux套接字相似。所以,只須要更改Linux環境下編好的一部分網絡程序內容,就能再Windows平臺下運行。

 

2,上面講了Linux上,文件操做和套接字操做一致。但Windows上的I/O函數和套接字I/O函數是不一樣的。
Winsock數據傳輸函數:

 

int send(SOCKET s, const char *buf, int len, int flags);

 

Winsock數據接收函數:

 

int recv(SOCKET s, const char *buf, int len, int flags);

 

3,Windows與Linux上的套接字再一個區別是:Windows上須要先對Winsock庫進行初始化,最後退出還要註銷Winsock相關庫。

 

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
第一個參數:Winsock中存在多個版本,應準備WORD類型的(WORD是typedef聲明的unsigned short)套接字版本信息。若版本爲1.2,則其中1是主版本號,2是副版本號,應傳遞0x0201。高8位爲副版本號,低8位爲主版本號。咱們還能夠直接使用宏,MAKEWORD(1,2); //主版本號爲1,副版本爲2,返回0x0201。
第二個參數:就是傳入WSADATA型結構體變量地址。

 

Winsock庫初始化:

int main(int  argc, char *argv[])
{
    WSADATA wsaData;
    ...
    if(WSAStartup(MAKEWORD(1,2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");
    ...
    return 0;
}

在退出時須要釋放Winsock庫:

int WSACleanup(void); //返回0成功,失敗返回SOCKET_ERROR

 

 

代碼:

windows下:

 

TCP:

一.服務端代碼

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

void main()
{
    WSADATA wsaData;
    int port = 5099;

    char buf[] = "Server: hello, I am a server.....";

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("Failed to load Winsock");
        return;
    }

    //建立用於監聽的套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port); //1024以上的端口號
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
    if (retVal == SOCKET_ERROR){
        printf("Failed bind:%d\n", WSAGetLastError());
        return;
    }

    if (listen(sockSrv, 10) == SOCKET_ERROR){
        printf("Listen failed:%d", WSAGetLastError());
        return;
    }

    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);

//等待客戶請求到來    
        SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, &len);
        if (sockConn == SOCKET_ERROR){
            printf("Accept failed:%d", WSAGetLastError());
            //break;
        }

        printf("Accept client IP:[%s]\n", inet_ntoa(addrClient.sin_addr));

        //發送數據
        int iSend = send(sockConn, buf, sizeof(buf), 0);
        if (iSend == SOCKET_ERROR){
            printf("send failed");
           // break;
        }

        char recvBuf[100];
        memset(recvBuf, 0, sizeof(recvBuf));
        //         //接收數據
        recv(sockConn, recvBuf, sizeof(recvBuf), 0);
        printf("%s\n", recvBuf);

        closesocket(sockConn);


    closesocket(sockSrv);
    WSACleanup();
    system("pause");
}

 

二.客戶端代碼

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

void main()
{
    //加載套接字
    WSADATA wsaData;
    char buff[1024];
    memset(buff, 0, sizeof(buff));

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("Failed to load Winsock");
        return;
    }

    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(5099);
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    //建立套接字
    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
    if (SOCKET_ERROR == sockClient){
        printf("Socket() error:%d", WSAGetLastError());
        return;
    }

    //向服務器發出鏈接請求
    if (connect(sockClient, (struct  sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET){
        printf("Connect failed:%d", WSAGetLastError());
        return;
    }
    else
    {
        //接收數據
        recv(sockClient, buff, sizeof(buff), 0);
        printf("%s\n", buff);
    }

    //發送數據
    char *buffSend = "hello, this is a Client....";
    send(sockClient, buffSend, strlen(buffSend) + 1, 0);
    printf("%d", strlen(buffSend) + 1);

    //關閉套接字
    closesocket(sockClient);
    WSACleanup();
    system("pause");
}

 

怕某些小白不懂我詳細說說運行,運行時先開服務端,在開客戶端運行 ,也就是開兩個cPP文件分別運行,兩個cpp各是一個小項目代碼 不要放在一塊兒

這裏的127.0.0.1是表明本地的地址,大家想實現兩機交互就用對方的地址。

 

代碼很簡單,想要直接拿去,接下來直接看運行結果:

 

 

 

UDP:

windows下UDP服務端代碼

#include<winsock2.h>
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#define BUFFER_SIZE 1024
int main()
{
  WSADATA WSAData;
  char receBuf[BUFFER_SIZE];
  char Response[BUFFER_SIZE];
  if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
  {
    printf("初始化失敗");
    exit(1);
  }
  SOCKET sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (sockServer == INVALID_SOCKET)
  {
    printf("Failed socket() \n");
    return 0;
  }
  SOCKADDR_IN addr_Server; //服務器的地址等信息
  addr_Server.sin_family = AF_INET;
  addr_Server.sin_port = htons(4567);
  addr_Server.sin_addr.S_un.S_addr = INADDR_ANY;
  if (bind(sockServer, (SOCKADDR*)&addr_Server, sizeof(addr_Server)) == SOCKET_ERROR)
  {
    //服務器與本地地址綁定
    printf("Failed socket() %d \n", WSAGetLastError());
    return 0;
  }
  SOCKADDR_IN addr_Clt;

  int fromlen = sizeof(SOCKADDR);
  while (true)
  {
    int last = recvfrom(sockServer, receBuf, 1024, 0, (SOCKADDR*)&addr_Clt, &fromlen);
    if (last>0)
    {
      //判斷接收到的數據是否爲空
      receBuf[last] = '\0';//給字符數組加一個'\0',表示結束了。否則輸出有亂碼
      if (strcmp(receBuf, "bye") == 0)
      {
        cout << " 客戶端不跟我聊天了..." << endl;
        closesocket(sockServer);
        return 0;
      }
      else
      {
        printf("接收到數據(%s):%s\n", inet_ntoa(addr_Clt.sin_addr), receBuf);
      }
    }
    cout << "回覆客戶端消息:";
    Response = 「」;     cin >> Response; //給客戶端回覆消息     sendto(sockServer, Response, strlen(Response), 0, (SOCKADDR*)&addr_Clt, sizeof(SOCKADDR));   }   closesocket(sockServer);   WSACleanup();   return 0; }

 

windows下UDP客戶端端代碼

 

#include<winsock2.h>
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
# define BUFFER_SIZE 1024 //緩衝區大小
int main()
{
  SOCKET sock_Client; //客戶端用於通訊的Socket
  WSADATA WSAData;
  char receBuf[BUFFER_SIZE]; //發送數據的緩衝區
  char sendBuf[BUFFER_SIZE]; //接受數據的緩衝區

  if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
  {
    printf("初始化失敗!");
    return -1;
  }

   //初始化
  sock_Client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//建立客戶端用於通訊的Socket
  SOCKADDR_IN addr_server; //服務器的地址數據結構
  addr_server.sin_family = AF_INET;
  addr_server.sin_port = htons(4567);//端口號爲4567
  addr_server.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //127.0.0.1爲本電腦IP地址
  SOCKADDR_IN sock;
  int len = sizeof(sock);
  while (true)
  {
    cout << "請輸入要傳送的數據:";
    cin >> sendBuf;
    sendto(sock_Client, sendBuf, strlen(sendBuf), 0, (SOCKADDR*)&addr_server, sizeof(SOCKADDR));
    //int last=recv(sock_Client, receBuf, strlen(receBuf), 0); // (調用recv和recvfrom均可以)
    int last = recvfrom(sock_Client, receBuf, strlen(receBuf), 0, (SOCKADDR*)&sock, &len);
    if (last>0)
    {
      receBuf[last] = '\0'; //給字符數組加一個'\0',表示結束了。否則輸出有亂碼
      if (strcmp(receBuf, "bye") == 0)
      {
        cout << "服務器不跟我聊天了..." << endl;//當服務器發來bye時,關閉socket
        closesocket(sock_Client);
        break;
      }
      else
      {
        printf("接收到數據:%s\n", receBuf);
      }

    }

  }
  closesocket(sock_Client);
  WSACleanup();


  return 0;
}

 

 

 

 

 

 

 

注:如下代碼需在LINUX下運行 gcc什麼的均可以

 

LINUX下

 

一.TCP

 

 

linux下TCP服務端代碼:

#include <stdio.h>
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int server_sockfd;//服務器端套接字 int client_sockfd;//客戶端套接字 int len; struct sockaddr_in my_addr; //服務器網絡地址結構體 struct sockaddr_in remote_addr; //客戶端網絡地址結構體 int sin_size; char buf[BUFSIZ]; //數據傳送的緩衝區 memset(&my_addr,0,sizeof(my_addr)); //數據初始化--清零 my_addr.sin_family=AF_INET; //設置爲IP通訊 my_addr.sin_addr.s_addr=INADDR_ANY;//服務器IP地址--容許鏈接到全部本地地址上 my_addr.sin_port=htons(8000); //服務器端口號 /*建立服務器端套接字--IPv4協議,面向鏈接通訊,TCP協議*/ if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) { perror("socket error"); return 1; } /*將套接字綁定到服務器的網絡地址上*/ if(bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) { perror("bind error"); return 1; } /*監聽鏈接請求--監聽隊列長度爲5*/ if(listen(server_sockfd,5)<0) { perror("listen error"); return 1; }; sin_size=sizeof(struct sockaddr_in); /*等待客戶端鏈接請求到達*/ if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0) { perror("accept error"); return 1; } printf("accept client %s/n",inet_ntoa(remote_addr.sin_addr)); len=send(client_sockfd,"Welcome to my server/n",21,0);//發送歡迎信息 /*接收客戶端的數據並將其發送給客戶端--recv返回接收到的字節數,send返回發送的字節數*/ while((len=recv(client_sockfd,buf,BUFSIZ,0))>0) { buf[len]='\0'; printf("%s/n",buf); if(send(client_sockfd,buf,len,0)<0) { perror("write error"); return 1; } } /*關閉套接字*/ close(client_sockfd); close(server_sockfd); return 0; }

 

linux下TCP客戶端代碼:

 

#include <stdio.h>
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int client_sockfd; int len; struct sockaddr_in remote_addr; //服務器端網絡地址結構體 char buf[BUFSIZ]; //數據傳送的緩衝區 memset(&remote_addr,0,sizeof(remote_addr)); //數據初始化--清零 remote_addr.sin_family=AF_INET; //設置爲IP通訊 remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服務器IP地址 remote_addr.sin_port=htons(8000); //服務器端口號 /*建立客戶端套接字--IPv4協議,面向鏈接通訊,TCP協議*/ if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) { perror("socket error"); return 1; } /*將套接字綁定到服務器的網絡地址上*/ if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0) { perror("connect error"); return 1; } printf("connected to server/n"); len=recv(client_sockfd,buf,BUFSIZ,0);//接收服務器端信息 buf[len]='\0'; printf("%s",buf); //打印服務器端信息 /*循環的發送接收信息並打印接收信息(能夠按需發送)--recv返回接收到的字節數,send返回發送的字節數*/ while(1) { printf("Enter string to send:"); scanf("%s",buf); if(!strcmp(buf,"quit") break; len=send(client_sockfd,buf,strlen(buf),0); len=recv(client_sockfd,buf,BUFSIZ,0); buf[len]='/0'; printf("received:%s/n",buf); } /*關閉套接字*/ close(client_sockfd); return 0; }

 

 

 

 

二.UDP

 

 

linux下UDP服務端代碼

#include <stdio.h>
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int server_sockfd; int len; struct sockaddr_in my_addr; //服務器網絡地址結構體 struct sockaddr_in remote_addr; //客戶端網絡地址結構體 int sin_size; char buf[BUFSIZ]; //數據傳送的緩衝區 memset(&my_addr,0,sizeof(my_addr)); //數據初始化--清零 my_addr.sin_family=AF_INET; //設置爲IP通訊 my_addr.sin_addr.s_addr=INADDR_ANY;//服務器IP地址--容許鏈接到全部本地地址上 my_addr.sin_port=htons(8000); //服務器端口號 /*建立服務器端套接字--IPv4協議,面向無鏈接通訊,UDP協議*/ if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0) { perror("socket error"); return 1; } /*將套接字綁定到服務器的網絡地址上*/ if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) { perror("bind error"); return 1; } sin_size=sizeof(struct sockaddr_in); printf("waiting for a packet.../n"); /*接收客戶端的數據並將其發送給客戶端--recvfrom是無鏈接的*/ if((len=recvfrom(server_sockfd,buf,BUFSIZ,0,(struct sockaddr *)&remote_addr,&sin_size))<0) { perror("recvfrom error"); return 1; } printf("received packet from %s:/n",inet_ntoa(remote_addr.sin_addr)); buf[len]='/0'; printf("contents: %s/n",buf); /*關閉套接字*/ close(server_sockfd); return 0; }

 

linux下UDP客戶端代碼

 

#include <stdio.h>
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int client_sockfd; int len; struct sockaddr_in remote_addr; //服務器端網絡地址結構體 int sin_size; char buf[BUFSIZ]; //數據傳送的緩衝區 memset(&remote_addr,0,sizeof(remote_addr)); //數據初始化--清零 remote_addr.sin_family=AF_INET; //設置爲IP通訊 remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服務器IP地址 remote_addr.sin_port=htons(8000); //服務器端口號 /*建立客戶端套接字--IPv4協議,面向無鏈接通訊,UDP協議*/ if((client_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0) { perror("socket error"); return 1; } strcpy(buf,"This is a test message"); // 發送的內容 printf("sending: '%s'/n",buf); sin_size=sizeof(struct sockaddr_in); /*向服務器發送數據包*/ if((len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0) { perror("recvfrom"); return 1; } /*關閉套接字*/ close(client_sockfd); return 0; }

 

 

 最後說一句啦。本網絡編程入門系列博客是連載學習的,有興趣的能夠看我博客其餘篇。。。。c++ 網絡編程課設入門超詳細教程 ---目錄

 

參考博客:https://blog.csdn.net/u012234115/article/details/54142273

參考博客:https://www.cnblogs.com/zwj-199306231519/p/9067618.html

參考博客:https://blog.csdn.net/u010223072/article/details/46771047

 

如有興趣交流分享技術,可關注本人公衆號,裏面會不按期的分享各類編程教程,和共享源碼,諸如研究分享關於c/c++,python,前端,後端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識

相關文章
相關標籤/搜索