目錄
前言
基本概念
源ip&&目的ip
源端口號&&目的端口號
udp&&tcp初識?
socket編程?
網絡字節序
socket常見接口
socket
bind
listen
accept
connect
地址轉換函數
字符串轉in_addr
in_addr轉字符串
套接字讀寫函數
recvfrom&&recv
sendto&&send
簡單udp服務器客戶端程序
udp服務器
udp客戶端
簡單tcp服務器客戶端程序
接口理解
tcp服務器?
tcp客戶端
后記
前言
? ? ? ? 在學習網絡部分知識時,只學習或只背誦概念是沒有用的,面試過程中面試官肯定會究其細節或理解從各方面考察,因此上章節介紹完網絡初步相關概念之后,我們先上代碼去直觀地了解網絡,為后面更深層次或者復雜的概念或網絡傳輸過程的細節打下基礎。那么,在網絡部分socket編程就是用于在不同計算機之間進行通信,因此本章節就是介紹socket編程相關接口以及運用其編寫簡單的程序去了解通信過程。
基本概念
-
源ip&&目的ip
? ? ? ? 在ip數據包的頭部中存在兩個IP地址——源ip&&目的ip,即此數據包從哪來&&去哪里。我們知道,IP地址(此處說的是公網ip),標定了主機的唯一性,但通過IP地址將數據發送到主機并不是完整的網絡通信過程,也就是說一臺主機的信息到達了目標主機上真的就算是數據發送成功了嗎?并不是,其實網絡通信過程的本質就是進程間通信(由一臺主機的進程發送到另一臺主機的進程),將數據在主機間轉發只是手段,主機收到后需要將數據交付給相應進程。但是,數據到達目標主機后該走向哪個進程呢?這就需要下一個概念——端口號了。
-
源端口號&&目的端口號
? ? ? ? 端口號(2字節16位的整數)是傳輸層的內容,標識特定主機上網絡進程的唯一性,也就是標識一個進程,告訴os數據交給哪個進程來處理。可見,一個端口號只能被一個進程占用,但一個進程可以綁定多個端口號。
? ? ? ??源端口號&&目的端口號存在于傳輸層的數據段中,標識哪個進程發的&&發給哪個進程。綜上ip+端口號表示指定主機的指定進程,這是一個數據進入網絡前要確定的東西。
-
udp&&tcp初識?
? ? ? ? 先來簡單初識一下TCP和UDP,在后面講解傳輸層時會詳解。TCP(Transmission Control Protocol 傳輸控制協議)和UDP(User Datagram Protocol 用戶數據報協議)都是傳輸層協議,但TCP需要連接、可靠傳輸、面向字節流,而UDP無需連接、不可靠傳輸、面向數據報,如下圖很具象地展現了TCP和UDP傳輸數據地過程。
? ? ? ? 其中,(無需)連接說的是udp沒有tcp在通信的三次握手與四次揮手,而是沒有建立連接過程,即“發送即結束”;可靠傳輸說的是tcp傳輸數據是不會出錯的,udp傳輸數據可以出錯;面向字節流與面向數據報是指看待數據的方式不同,tcp需要根據應用層協議對字節流作序列化和反序列化識別出一個報文,而udp直接默認拿到的就是一個報文。但是,綜上來看真的意思是tcp比udp好嗎?其實不然,兩者的使用需要看場景,tcp可以多應用在需要數據準確交付的場景,比如重要文件傳輸,而udp可以多應用在對傳輸錯誤也可容忍的場景,比如游戲,直播等實時性要求高的環境下(有時看直播會卡,但是無傷大雅)。
-
socket編程?
????????Socket編程是一種網絡編程技術,它提供了一種在不同計算機之間進行通信的方式。
基于TCP/IP協議,它允許計算機之間通過網絡進行數據傳輸。Socket編程提供了一些函數和方法,用于建立連接、傳輸數據和關閉連接。
????????在Socket編程中,有兩個主要的角色:服務器和客戶端。服務器程序監聽指定的端口,等待客戶端的連接請求。客戶端程序通過指定服務器的IP地址和端口,發起連接請求。以上會在下面詳細介紹。
網絡字節序
? ? ? ? 我們知道,內存中的多字節數據相對于內存地址有大端和小端之分,而網絡數據流也同樣有大端小端之分,因此,TCP/IP協議規定,網絡數據流應采用大端字節序,即低地址高字節.。不管這臺主機是大端機還是小端機,都要按照這個TCP/IP規定的網絡字節序來發送/接收數據,如果當前發送主機是小端,就需要先將數據轉成大端,否則就忽略,直接發送即可。那么,如下接口就是轉大端的相關接口,調用其可以做網絡字節序和主機字節序的轉換。
? ? ? ? 其中h表示host,n表示net,l表示32位長整數,s表示16位短整數,如果主機是小端字節序,這些函數將參數做相應的大小端轉換然后返回,如果主機是大端字節序,這些函數將不做轉換,將參數原封不動的返回。
socket常見接口
-
socket
功能:創建socket文件描述符(tcp和udp均會用到,服務器和客戶端均需要)
參數:
? ? ? ? domain:對于ipv4,指定為AF_INET,對于ipv6,指定為AF_INET6
? ? ? ? type:對于UDP,指定為SOCK_DGRAM,表示面向數據報的傳輸協議,對于TCP,指定為SOCK_STREAM,表示面向字節流的傳輸協議
? ? ? ? protocol:指定為0即可
返回值:成功返回一個文件描述符,類似讀寫文件一樣,向此文件描述符讀寫就是在網絡上收發數據,否則返回-1
-
bind
功能:綁定端口號(tcp和udp均會用到,僅服務器需要,因為服務器程序所監聽的網絡地址和端口號通常是固定不變的,客戶端程序得知服務器程序的地址和端口號后就可以向服務器發起連接,但是客戶端程序的端口號通常是變化的,所以不需要bind),將參數sockfd和myaddr綁定在一起,使sockfd這個用于網絡通訊的文件描述符監聽addr所描述的地址和端口號
參數:
? ? ? ? sockfd:需要綁定的sock套接字,其實就是socket函數的返回值
? ? ? ? addr:通用指針類型,可以接受多種協議的sockaddr結構體地址(多種sockaddr結構體類型在下面介紹),但長短不同,需要傳入長度
? ? ? ? addrlen:addr指向的結構體長度
返回值:成功返回0,否則返回-1
sockaddr結構:?
?????????socket的接口適用于各種底層網絡協議,但是各種網絡協議的地址格式并不相同,又不想設計過多的接口,因此將所有接口進行統一,接口只有一套,協議格式統一使用struct sockaddr*,傳其他協議地址(sockaddr_in、sockaddr_un,如下圖)需要通過強轉,但若需要知道是哪一種套接字地址,可以通過16位地址類型判斷。
? ? ? ? 這三者中,本章節常用到sockaddr_in結構,也就是說常用到將sockaddr結構體強轉成sockaddr_in結構體,這里詳細說明一下sockaddr_in結構體,以下為其內部結構,在這個結構體中,最主要的就是端口號和IP地址,即圖中標星號的部分。
? ? ? ? 在我們寫的程序中一般是這樣初始化sockaddr結構體的(如下代碼塊),首先清零,再指定地址類型,然后填入ip和端口號信息。注意轉大端字節序,并且其中INADDR_ANY為宏(實則為0.0.0.0),表示本地的任意IP地址,則最后一個語句代表若需要指定ip地址則賦值給_ip,否則使用任意IP地址。
代碼:
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(_port);
peer.sin_addr.s_addr = _ip.size() == 0 ? INADDR_ANY : inet_addr(_ip.c_str());
-
listen
功能:開始監聽socket(僅tcp會用到,僅服務器需要)
參數:
? ? ? ? sockfd:如上
? ? ? ? backlog:表示最多允許有backlog個客戶端處于連接等待狀態,如果接收到更多的連接請求就忽略,常設置為20,后面會詳細講到
返回值:成功返回0,否則返回-1
-
accept
功能:接受請求(僅tcp會用到,僅服務器需要),三次握手完成后,服務器調用accept()接受連接,如果服務器調用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來
參數:
? ? ? ? sockfd:如上
????????addr和addrlen:輸出型參數,需提前定義傳入函數,函數返回后帶出來對方的ip和端口號信息,若傳NULL,則代表不關心客戶端的地址
返回值:成功返回一個文件描述符,否則返回-1
-
connect
功能:建立連接(僅tcp會用到,僅客戶端需要,因為客戶端需要調用此函數連接服務器)
參數:
? ? ? ? sockfd:自己(客戶端)的fd
? ? ? ? addr和addrlen:傳入對方的IP地址和端口號信息
返回值:成功返回0,失敗返回-1
地址轉換函數
? ? ? ? 在上面介紹到的sockaddr中,sockaddr_in的成員sin_addr(struct in_addr)表示32位的IP地址,但是我們通常都是用點分十進制(比如192.168.1.10)表示IP地址,因此需要函數可以將字符串表示和in_addr之間來回轉換。
-
字符串轉in_addr
? ? ? ? 首先,三者的功能是一樣的——將字符串轉in_addr,先看前兩個,cp就是需要轉換的字符串,inp是一個輸出型參數,結果由此參數傳出,而第二個函數是直接返回出來,第三個函數較于前兩個函數是新的,其中af可以填AF_INET或AF_INET6表示ipv4或ipv6,src還是傳入需要轉換的字符串,dst與inp的功能是一樣的,只是需要接收ipv4的in_addr或ipv6的in6_addr,因此設置為void*可以同樣接收(強轉)。
-
in_addr轉字符串
? ? ? ? 同樣地,in傳入in_addr,函數返回轉換地字符串形式,而inet_ntop是新接口,可以接收ipv6,src就是傳入不同類型的in_addr,dst接收轉換的字符串指針,size傳入字符串指針可以接收的字符串大小。
套接字讀寫函數
-
recvfrom&&recv
? ? ? ? 注意,recv用于tcp,recvfrom函數用于udp。可以看到,recvfrom函數除了比recv函數多了兩個參數,其他的參數是一樣的,因此只要介紹recvfrom即可。
sockfd:讀寫的套接字(文件描述符)
buffer&&len:讀取的數據所放的緩沖區及大小
flags:控制接收行為,通常可設為0
src_addr&&addrlen:輸出型參數,用來接收數據來源的地址信息及大小,注意addrlen在傳入時就要是sre_addr的大小
返回值:正常返回接收成功的字符個數若數據讀完或套接字關閉,則返回值為0,若出錯則返回-1并設置錯誤碼
-
sendto&&send
????????注意,send用于tcp,sendto函數用于udp。同樣地,sendto函數也是比send函數多了兩個參數。
sockfd:讀寫的套接字(文件描述符)
buf&&len:指向需要發送的數據的字符串指針及大小
flags:同recvfrom函數
dest_addr&&addrlen:不是輸出型函數,需要傳入需要發送的目的地址信息及大小
返回值:同recvfrom函數
注意:
????????思考一下recvfrom函數和sendto函數的使用順序,比如說,你使用sendto函數給張三發信息,此時你需要知道張三的地址信息,張三使用recvfrom函數接收到了你的信息,并且他通過輸出型參數得到了你的地址信息,之后張三拿著你的地址信息使用sendto函數給你回信,你通過recvfrom函數拿到了張三的回信并且拿到了他的地址信息。
? ? ? ? 在這個過程中,你與張三通信的前提是你知道張三的地址信息,你其實是知道的,無論是之前見面留下的聯系方式還是曾經通信過,你們之間必須至少有一方是知道的,否則無法通信。類比到服務器與客戶端,“你”就是客戶端,“張三”就是服務器,一般來說都是客戶端主動給服務器發信息,客戶端都是提前知道服務器的地址信息的,客戶端給服務器發送請求,服務器處理需求,將結果回信給客戶端,客戶端拿到需求即為享受到服務。
簡單udp服務器客戶端程序
-
udp服務器
? ? ? ? 下面的代碼塊是udp服務器的封裝及主程序調用。先看udp服務器封裝的類,屬性包括監聽的文件描述符sockfd、IP地址、端口,這些是一個udp服務器最基本需要的屬性,構造函數中傳入所決定的端口號、IP地址、sock(還沒有生成,初始化為-1)。
? ? ? ? 在啟動udp服務器前,先做一下準備工作——initserver(),創建監聽的文件描述符sock和綁定地址信息(包括端口號和IP地址),之后啟動udp服務器——start(),顯然是死循環運行,作為服務器,我們需要先使用recvfrom函數接收客戶端的請求(數據),返回值大于0,說明正確讀到了數據,這里當作字符串打印出來,并調用sendto函數將讀到的字符串寫回給客戶端(處理方式有很多,也可以客戶端傳來兩個數字,服務器計算出結果將其返回,但是這涉及到http協議的設定及序列化、反序列化)。最后若關閉服務器,莫要忘記close監聽的sockfd。
? ? ? ? 可以看到,udp服務器只能用到前面所談到的socket常用接口其中的兩個——socket()、bind(),這可以聯想到udp的特性——無連接,而listen()、accept()、connect()意味著是有連接,在tcp服務器的實現中才會用到。
代碼:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
using std::cout;
using std::endl;
#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port,std::string ipAddr=""):_port(port),_ipAddr(ipAddr),_sock(-1){}bool initserver(){//1.創建套接字(ip+port)_sock=socket(AF_INET,SOCK_DGRAM,0);if(_sock<0){logMessage(FATAL,"%d:%s",errno,strerror(errno));exit(2); }logMessage(NORMAL,"create socket success");//2.通過bind函數將此ip和端口號與此進程綁定struct sockaddr_in local;bzero(&local,sizeof(local)); //設置內容之前將所有字段設置為0local.sin_family=AF_INET;//端口需要發送給對方主機,需要通過網絡,因此將數據的字節序改為大端存儲local.sin_port=htons(_port);//ip也是一樣,但同時還需要將ip的字符串形式改為4個字節存儲,因此4個字節足夠存儲一個ip,//比如說192.168.10.1,每段數字都是[0,255],用1字節即可存儲//因此使用inet_addr函數,可同時完成上面兩件事//同時,INADDR_ANY表示服務器在工作過程中,可以從任意IP中獲取數據,也就是說不建議bind一個確定的地址local.sin_addr.s_addr = _ipAddr.size()==0 ? INADDR_ANY : inet_addr(_ipAddr.c_str());if(bind(_sock,(struct sockaddr*)&local,sizeof(local))<0){logMessage(FATAL,"%d:%s",errno,strerror(errno));exit(3);}logMessage(NORMAL,"bind success");return true;}void start(){char buffer[SIZE];for( ; ; ){//讀取數據struct sockaddr_in peer;socklen_t len=sizeof(peer);ssize_t rf=recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(rf>0){buffer[rf]=0; //緩沖區末尾放上‘\0’,當作字符串uint16_t cli_port=ntohs(peer.sin_port);std::string cli_ip=inet_ntoa(peer.sin_addr);printf("[%s:%d]: %s\n",cli_ip.c_str(),cli_port,buffer);}//分析處理數據//寫回數據sendto(_sock,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,len);}}~UdpServer(){if(_sock>=0) close(_sock);}
private:uint16_t _port;std::string _ipAddr;int _sock;
};static void usage(std::string proc)
{cout<<"\nUsage:"<<proc<<" port\n"<<endl;
}int main(int argc,char* argv[])
{if(argc!=2){usage(argv[0]);exit(1);}//std::string ip=argv[1];uint16_t port=stoi(argv[1]);std::unique_ptr<UdpServer> server(new UdpServer(port));server->initserver();server->start();return 0;
}
-
udp客戶端
? ? ? ? 下面代碼塊是udp客戶端的實現(主程序實現,未封裝udp客戶端),與服務器實現并沒有本質的不同(但可以發現客戶端不需要bind)。通過命令行參數傳入服務器的地址信息(端口號和ip地址),首先依舊是創建監聽的文件描述符sock,之后就可以將服務器的地址信息填進struct sockaddr_in中,等待后面的sendto函數的使用,但是會發現客戶端無需bind,為什么?因為客戶端通過哪個端口與服務器通信并不重要,socket函數的函數體中會自動為客戶端程序選擇一個未被占用的端口號,并不需要用戶去操心,那又為什么服務器程序需要指定端口號呢?因為服務器的端口號和IP地址是需要固定的,不然客戶端程序如何得知新的變化的端口號和IP地址,因此需要調用bind函數固定下來。
? ? ? ? 顯然,客戶端程序主體也是一個死循環,先產生用戶的需求,之后調用sendto函數將需求發給服務器程序,服務器處理之后,客戶端調用rcvfrom函數接收結果,這里將結果顯示出來(舉例而已,也可以將結果作為下一次傳入的需求),若客戶端程序關閉,也莫要忘記關閉監聽的文件描述符。
代碼:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using std::cout;
using std::endl;
#define SIZE 1024static void usage(std::string proc)
{cout<<"\nUsage:"<<proc<<" ip port\n"<<endl;
}int main(int argc,char* argv[]) //傳入服務器ip、port
{if(argc!=3){usage(argv[0]);exit(1);}int sock=socket(AF_INET,SOCK_DGRAM,0);if(sock<0){std::cerr<<"socket error"<<endl;exit(2);}uint16_t serverport=atoi(argv[2]);std::string serverip=argv[1];std::string message; //從鍵盤拿信息char buffer[SIZE]; //緩沖區struct sockaddr_in server;memset(&server,0,sizeof(server)); //提前置零server.sin_addr.s_addr=inet_addr(serverip.c_str());server.sin_port=htons(serverport);while(true){cout<<"請輸入:";std::getline(std::cin,message);if(message=="quit")break;sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));struct sockaddr_in tmp;socklen_t len=sizeof(tmp);ssize_t rf=recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp,&len); //tmp與server相同,都是服務端ip、port相關信息if(rf>0){buffer[rf]=0;cout<<"server # "<<buffer<<endl;}}close(sock);return 0;
}
?運行結果:
簡單tcp服務器客戶端程序? ? ? ??
-
tcp服務器?
? ? ? ? 以下代碼塊是tcp服務器的封裝及主程序調用,先看tcp服務器封裝,屬性和構造函數與udp服務器封裝一樣。對于initserver(),可以看到,調用socket創建一個文件描述符及bind()綁定地址信息也都是一樣的,但是tcp服務器緊接著調用listen()監聽sockfd是否有客戶端連接,這是tcp服務器必須要做的,其實本質就是使sockfd成為一個監聽描述符。
? ? ? ? 之后,依舊是死循環開啟tcp服務器——start()。首先,服務器會調用accept函數去阻塞等待客戶端的連接,當有客戶端調用connect函數去建立連接時,accept函數就會返回一個新的文件描述符serverfd,這個文件描述符專門用來和客戶端通信,而原本的監聽描述符繼續監聽客戶端的connect函數申請連接,若存在就會繼續返回另一個新的文件描述符與此客戶端通信。這個過程就好像是,監聽文件描述符是一個在飯店門口拉客的服務員,當拉到客人之后進店就會找另一個服務員(與此客戶端通信的文件描述符)來接待這個客人(比如點菜,服務),而拉客服務員繼續去門外拉客。
? ? ? ? 當accept函數返回一個新的文件描述符來與客戶端通信,服務器就可以通過此文件描述符收到客戶端的需求,并返回應答給客戶端,這里封裝成了一個服務函數——service(),首先recv函數(也可以read函數)讀取客戶端的數據,處理以后通過write函數(也可以send函數)返回結果給客戶端,這里依舊是把數據當作字符串并寫回給客戶端。
代碼:
#include <iostream>
#include <string>
#include <string.h>
#include <cstdlib>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"using std::cout;
using std::endl;
using std::string;static void service(int servicesock,string cli_ip,uint16_t cli_port)
{char buffer[1024];while(true){int n=read(servicesock,buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\0';cout<<cli_ip<<"-"<<cli_port<<"# "<<buffer<<endl;}else if(n==0) //連接被關閉了{logMessage(NORMAL,"link shutdown");break;}else{logMessage(ERROR,"read error");break;}string msg;msg+=buffer;write(servicesock,msg.c_str(),msg.size());}
}class TcpServer
{
private:const static int g_backlog=20;
public:TcpServer(uint16_t port,string ip=""):_port(port),_ip(ip),_sock(-1){}void initserver(){//創建socket_sock=socket(AF_INET,SOCK_STREAM,0);if(_sock<0){logMessage(FATAL,"create socket error");exit(2);}//bindstruct sockaddr_in peer;memset(&peer,0,sizeof(peer));peer.sin_port=htons(_port);peer.sin_family=AF_INET;peer.sin_addr.s_addr=_ip.size()==0 ? INADDR_ANY : inet_addr(_ip.c_str());if(bind(_sock,(struct sockaddr*)&peer,sizeof(peer))<0){logMessage(FATAL,"bind error");exit(3);}//listen函數用以建立連接,相當于飯店的一個服務員在外面拉客,其中_sock就是在外面拉客的服務員if(listen(_sock,g_backlog)<0){logMessage(FATAL,"listen error");exit(4);}logMessage(NORMAL,"initserver success");}void start(){while(true){struct sockaddr_in peer;socklen_t len=sizeof(peer);int servicesock=accept(_sock,(struct sockaddr*)&peer,&len); //_sock將拉的客人帶到飯店,店里接待的服務員出來一個招呼他,其中servicesock就是一個招待的服務員if(servicesock<0){logMessage(ERROR,"accept error");continue;}logMessage(NORMAL,"accept success");uint16_t clientport=ntohs(peer.sin_port);string clientip=inet_ntoa(peer.sin_addr);service(servicesock,clientip,clientport);}}~TcpServer(){if(_sock>0)close(_sock);}
private:uint16_t _port;string _ip;int _sock;
};static void usage(string proc)
{cout<<"usage: "<<proc<<" port"<<endl;
}// ./tcpserver port
int main(int argc,char* argv[])
{if(argc!=2){usage(argv[0]);exit(1);}uint16_t serverport=atoi(argv[1]);unique_ptr<TcpServer> server(new TcpServer(serverport));server->initserver();server->start();return 0;
}
-
tcp客戶端
? ? ? ? tcp客戶端也并未做出封裝,依舊是在主程序中直接實現,與udp客戶端程序實現的差別在于socket函數創建文件描述符(依舊不需要bind函數)之后,調用connect函數與服務器建立連接,成功以后就可以正常的通信了,這一點與udp客戶端實現也是一樣。
? ? ? ? 這里重點講的就是,其實connect函數就是在發起三次握手,三次握手成功以后服務器的accept函數就會返回一個與客戶端通信的文件描述符。也可以提一嘴,四次揮手是在客戶端關閉(close函數)了socket函數創建出的文件描述符,詳細會在傳輸層詳解中講到。
代碼:
#include <iostream>
#include <string>
#include <string.h>
#include <cstdlib>
#include <cstdio>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "thread.hpp"
#include "log.hpp"using std::cout;
using std::cerr;
using std::endl;
using std::string;static void usage(string proc)
{cout<<"usage: "<<proc<<" port"<<endl;
}// ./tcpclient serverip serverport
int main(int argc,char* argv[])
{if(argc!=3){usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=atoi(argv[2]);int sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){cerr<<"create socket error"<<endl;exit(2);}struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(serverport);server.sin_addr.s_addr=inet_addr(serverip.c_str());if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){cerr<<"connect error"<<endl;exit(3);}cout<<"connect success"<<endl;while(true){string msg;cout<<"請輸入:";getline(cin,msg);if(msg=="quit")break;send(sock,msg.c_str(),msg.size(),0);char buffer[1024];ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);if(s>0){buffer[s]=0;cout<<"server# "<<buffer<<endl;}else if(s==0){cout<<"link break"<<endl;break;}elsebreak;}close(sock);return 0;
}
?運行結果:
后記
? ? ? ? 在本章節中,我們先從網絡編程所需要的概念基礎下手,再從運用的視角去詳細介紹了各種供數據在網絡中傳輸的socket接口,接著編寫簡單的udp、tcp服務器綜合使用這些接口加以鞏固。雖接口看起來很多并且細節也多而且復雜,但是用于網絡數據傳輸的接口大部分也就涉及到這些,也就是說只要掌握這些,網絡傳輸的功能就掌握了一大半了,因此在學習這些接口時要仔細,最后的簡單udp、tcp服務器可以反復的獨立編寫,加強記憶,在學習后面較為復雜的服務器時可以相對輕松,加油。