linux網(wǎng)絡(luò)編程系列(三)--tcp和udp的基本函數(shù)調(diào)用過(guò)程及如何選擇

1. socket編程

1.1 概述

TCP是TCP/IP體系中面向連接的傳輸層協(xié)議蒙兰,它提供全雙工和可靠交付的服務(wù)。它采用許多機(jī)制來(lái)確保端到端結(jié)點(diǎn)之間的可靠數(shù)據(jù)傳輸雪情,如采用序列號(hào)冬念、確認(rèn)重傳支竹、滑動(dòng)窗口等。

首先泳唠,TCP要為所發(fā)送的每一個(gè)報(bào)文段加上序列號(hào)狈网,保證每一個(gè)報(bào)文段能被接收方接收,并只被正確的接收一次警检。

其次孙援,TCP采用具有重傳功能的積極確認(rèn)技術(shù)作為可靠數(shù)據(jù)流傳輸服務(wù)的基礎(chǔ)。這里“確認(rèn)”是指接收端在正確收到報(bào)文段之后向發(fā)送端回送一個(gè)確認(rèn)(ACK)信息扇雕。發(fā)送方將每個(gè)已發(fā)送的報(bào)文段備份在自己的緩沖區(qū)里拓售,而且在收到相應(yīng)的確認(rèn)之前是不會(huì)丟棄所保存的報(bào)文段的∠夥睿“積極”是指發(fā)送發(fā)在每一個(gè)報(bào)文段發(fā)送完畢的同時(shí)啟動(dòng)一個(gè)定時(shí)器础淤,加入定時(shí)器的定時(shí)期滿而關(guān)于報(bào)文段的確認(rèn)信息還沒(méi)有達(dá)到,則發(fā)送發(fā)認(rèn)為該報(bào)文段已經(jīng)丟失并主動(dòng)重發(fā)哨苛。為了避免由于網(wǎng)絡(luò)延時(shí)引起遲到的確認(rèn)和重復(fù)的確認(rèn)鸽凶,TCP規(guī)定在確認(rèn)信息中捎帶一個(gè)報(bào)文段的序號(hào),使接收方能正確的將報(bào)文段與確認(rèn)聯(lián)系起來(lái)建峭。

最后玻侥,采用可變長(zhǎng)的滑動(dòng)窗口協(xié)議進(jìn)行流量控制,以防止由于發(fā)送端與接收端之間的不匹配而引起的數(shù)據(jù)丟失亿蒸。這里所采用的滑動(dòng)窗口協(xié)議與數(shù)據(jù)鏈路層的滑動(dòng)窗口協(xié)議在工作原理上完全相同凑兰,唯一的區(qū)別在于滑動(dòng)窗口協(xié)議用于傳輸層是為了在端對(duì)端節(jié)點(diǎn)之間實(shí)現(xiàn)流量控制掌桩,而用于數(shù)據(jù)鏈路層是為了在相鄰節(jié)點(diǎn)之間實(shí)現(xiàn)流量控制。TCP采用可變長(zhǎng)的滑動(dòng)窗口姑食,使得發(fā)送端與接收端可根據(jù)自己的CPU和數(shù)據(jù)緩存資源對(duì)數(shù)據(jù)發(fā)送和接收能力來(lái)進(jìn)行動(dòng)態(tài)調(diào)整波岛,從而靈活性更強(qiáng),也更合理音半。

1.2 tcp服務(wù)端編程

1.2.1 TCP通信的基本步驟

服務(wù)端:socket---bind---listen---while(1){---accept---recv---send---close---}---close
客戶端:socket----------------------------------connect---send---recv-----------------close

1.2.2 服務(wù)器端頭文件包含

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

1.2.3 socket函數(shù)

功能:生成一個(gè)套接口描述符
原型:int socket(int domain,int type,int protocol);
參數(shù):
domain{ AF_INET:Ipv4網(wǎng)絡(luò)協(xié)議 AF_INET6:IPv6網(wǎng)絡(luò)協(xié)議}
type{tcp:SOCK_STREAM udp:SOCK_DGRAM}
protocol 指定socket所使用的傳輸協(xié)議編號(hào)则拷,常用的協(xié)議有:IPPROTO_TCP、IPPROTO_UDP曹鸠、IPPROTO_SCTP煌茬、IPPROTO_TIPC等,他們分別對(duì)應(yīng)TCP協(xié)議物延、UDP協(xié)議宣旱、STCP協(xié)議、TIPC協(xié)議叛薯,當(dāng)protocol默認(rèn)為0時(shí)浑吟,則根據(jù)type參數(shù)的值,自動(dòng)選擇協(xié)議類(lèi)型耗溜;
返回值:成功則返回套接口描述符组力,失敗返回-1。
常用實(shí)例:

int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1)
{
    perror("socket");
    exit(-1);
}

1.2.4 bind函數(shù)

功能:用來(lái)綁定一個(gè)端口號(hào)和IP地址抖拴,使套接口與指定的端口號(hào)和IP地址相關(guān)聯(lián)燎字。
原型:int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
參數(shù):
sockfd為前面socket的返回值
addrlen sockaddr的結(jié)構(gòu)體長(zhǎng)度。通常是計(jì)算sizeof(struct sockaddr);
my_addr為結(jié)構(gòu)體指針變量
對(duì)于不同的socket domain定義了一個(gè)通用的數(shù)據(jù)結(jié)構(gòu):

//此結(jié)構(gòu)體不常用
struct sockaddr 
{    
    unsigned short int sa_family;  //調(diào)用socket()時(shí)的domain參數(shù)阿宅,即AF_INET值候衍。    
    char sa_data[14];  //最多使用14個(gè)字符長(zhǎng)度
};
//此sockaddr結(jié)構(gòu)會(huì)因使用不同的socket domain而有不同結(jié)構(gòu)定義, 例如使用AF_INET domain洒放,其socketaddr結(jié)構(gòu)定義便為:
struct sockaddr_in  //常用的結(jié)構(gòu)體
{    
    unsigned short int sin_family;  //即為sa_family AF_INET    
    uint16_t sin_port;  //為使用的port編號(hào)    
    struct in_addr sin_addr;  //為IP 地址    
    unsigned char sin_zero[8];  //未使用
};

struct in_addr
{    
    uint32_t s_addr;
};

返回值:成功則返回0蛉鹿,失敗返回-1,并設(shè)置errno往湿,最常見(jiàn)的errno有以下兩種:

  • EACCES,被綁定的地址是受保護(hù)的地址妖异,僅超級(jí)用戶能夠訪問(wèn),比如如果綁定在1-1023端口的時(shí)候领追,就會(huì)報(bào)該錯(cuò)誤他膳。
  • EADDRINUSE,被綁定的地址正在使用中绒窑,比如將socket綁定在一個(gè)處于TIME_WAIT狀態(tài)的socket地址棕孙。

常用實(shí)例:

struct sockaddr_in my_addr;  //定義結(jié)構(gòu)體變量
memset(&my_addr, 0, sizeof(struct sockaddr)); //將結(jié)構(gòu)體清空
//或bzero(&my_addr, sizeof(struct sockaddr));
my_addr.sin_family = AF_INET;  //表示采用Ipv4網(wǎng)絡(luò)協(xié)議
my_addr.sin_port = htons(8888);  //表示端口號(hào)為8888,通常是大于1024的一個(gè)值。
//htons()用來(lái)將參數(shù)指定的16位hostshort轉(zhuǎn)換成網(wǎng)絡(luò)字符順序
my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); // //inet_addr()用來(lái)將IP地址字符串轉(zhuǎn)換成網(wǎng)絡(luò)所使用的二進(jìn)制數(shù)字蟀俊,如果為INADDR_ANY分歇,這表示服務(wù)器自動(dòng)填充本機(jī)IP地址。
if(bind(sfd, (struct sockaddr*)&my_str, sizeof(struct socketaddr)) == -1)
    {perror("bind");close(sfd);exit(-1);}
/*通過(guò)將my_addr.sin_port置為0欧漱,函數(shù)會(huì)自動(dòng)為你選擇一個(gè)未占用的端口來(lái)使用。同樣葬燎,通過(guò)將my_addr.sin_addr.s_addr置為INADDR_ANY误甚,系統(tǒng)會(huì)自動(dòng)填入本機(jī)IP地址*/

注意:如果my_addr.sin_addr.s_addr = htonl(INADDR_ANY)時(shí),INADDR_ANY就是指定地址為0.0.0.0的地址谱净,它其實(shí)是表示不確定地址窑邦,一般是用于多網(wǎng)卡的機(jī)器上,也就是有多個(gè)IP地址壕探,如果設(shè)置了INADDR_ANY冈钦,那么根據(jù)端口號(hào),無(wú)論連接哪個(gè)IP地址李请,都是可以連接成功的瞧筛。

1.2.5 listen函數(shù)

功能:使服務(wù)器的這個(gè)端口和IP處于監(jiān)聽(tīng)狀態(tài),等待網(wǎng)絡(luò)中某一客戶機(jī)的連接請(qǐng)求导盅。如果客戶端有連接請(qǐng)求较幌,端口就會(huì)接受這個(gè)連接。
原型:int listen(int sockfd, int backlog);
參數(shù):
sockfd為前面socket的返回值.即sfd
backlog指定同時(shí)能處理的最大連接要求白翻,通常為10或者5乍炉。 最大值可設(shè)至128
返回值:成功則返回0,失敗返回-1
常用實(shí)例:

if(listen(sfd, 10) == -1)
{
    perror("listen");
    close(sfd);
    exit(-1);
}

1.2.6 accept函數(shù)

功能:接受遠(yuǎn)程計(jì)算機(jī)的連接請(qǐng)求滤馍,建立起與客戶機(jī)之間的通信連接岛琼。服務(wù)器處于監(jiān)聽(tīng)狀態(tài)時(shí),如果某時(shí)刻獲得客戶機(jī)的連接請(qǐng)求巢株,此時(shí)并不是立即處理這個(gè)請(qǐng)求槐瑞,而是將這個(gè)請(qǐng)求放在等待隊(duì)列中,當(dāng)系統(tǒng)空閑時(shí)再處理客戶機(jī)的連接請(qǐng)求纯续。當(dāng)accept函數(shù)接受一個(gè)連接時(shí)随珠,會(huì)返回一個(gè)新的socket標(biāo)識(shí)符,以后的數(shù)據(jù)傳輸和讀取就要通過(guò)這個(gè)新的socket編號(hào)來(lái)處理猬错,原來(lái)參數(shù)中的socket也可以繼續(xù)使用窗看,繼續(xù)監(jiān)聽(tīng)其它客戶機(jī)的連接請(qǐng)求。
原型:int accept(int s,struct sockaddr * addr,int * addrlen);
參數(shù):
s為前面socket的返回值倦炒,即sfd
addr為結(jié)構(gòu)體指針變量显沈,和bind的結(jié)構(gòu)體是同種類(lèi)型的,系統(tǒng)會(huì)把遠(yuǎn)程主機(jī)的信息(遠(yuǎn)程主機(jī)的地址和端口號(hào)信息)保存到這個(gè)指針?biāo)傅慕Y(jié)構(gòu)體中。
addrlen表示結(jié)構(gòu)體的長(zhǎng)度拉讯,為整型指針
返回值:成功則返回新的文件描述符new_fd涤浇,失敗返回-1
常用實(shí)例:

struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(struct sockaddr));
int addrlen = sizeof(struct sockaddr);
int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);
if(new_fd == -1)
{
    perror("accept");
    close(sfd);
    exit(-1);
}
printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

注意:accept函數(shù)只是從監(jiān)聽(tīng)隊(duì)列中取出連接,而不論連接處于什么狀態(tài)魔慷,網(wǎng)絡(luò)狀況有什么變化只锭。

1.2.7 recv函數(shù)

功能:接收遠(yuǎn)端主機(jī)傳來(lái)的數(shù)據(jù),并把數(shù)據(jù)存到由參數(shù)buf 指向的內(nèi)存空間
原型:int recv(int sockfd,void *buf,int len,unsigned int flags);
參數(shù):sockfd為前面accept的返回值.即new_fd院尔,也就是新的套接字蜻展。
buf表示緩沖區(qū)
len表示緩沖區(qū)的長(zhǎng)度
flags通常為0
返回值:
>0 成功,返回實(shí)際接收到的字符數(shù)邀摆;
=0 連接關(guān)閉纵顾,說(shuō)明recv在等待接收數(shù)據(jù)時(shí)網(wǎng)絡(luò)中斷了;
-1 表示出錯(cuò)栋盹;
常用實(shí)例:

char buf[512] = {0};
if(recv(new_fd, buf, sizeof(buf), 0) == -1)
{
    perror("recv");
    close(new_fd);
    close(sfd);
    exit(-1);
}
puts(buf);

注意:read函數(shù)返回值不一樣施逾,大于0 是返回字節(jié)數(shù),等于0是讀到文件末尾了例获,小于0則表示出現(xiàn)了錯(cuò)誤汉额,如果錯(cuò)誤為EINTR說(shuō)明是由中斷引起的,如果是ECONNNERST則表示網(wǎng)絡(luò)連接出現(xiàn)了問(wèn)題躏敢。

1.2.8 send函數(shù)

功能:發(fā)送數(shù)據(jù)給指定的遠(yuǎn)端主機(jī)
原型:int send(int s,const void * msg,int len,unsigned int flags);
參數(shù):s為前面accept的返回值.即new_fd
msg一般為常量字符串
len表示長(zhǎng)度
flags通常為0
返回值:
>0 成功闷愤,返回實(shí)際傳送出去的字符數(shù),可能會(huì)少于你所指定的發(fā)送長(zhǎng)度件余;
=0 連接關(guān)閉讥脐,網(wǎng)絡(luò)中斷了;
-1 表示出錯(cuò)啼器;
常用實(shí)例:

if(send(new_fd, "hello", 6, 0) == -1)
{
    perror("send");
    close(new_fd);
    close(sfd);
    exit(-1);
}

注意:write函數(shù)返回小于0時(shí)旬渠,如果為EPIPE,則表示網(wǎng)絡(luò)連接出現(xiàn)了問(wèn)題端壳。

1.2.9 close函數(shù)

功能:當(dāng)使用完文件后若已不再需要?jiǎng)t可使用close()關(guān)閉該文件告丢,并且close()會(huì)讓數(shù)據(jù)寫(xiě)回磁盤(pán),并釋放該文件所占用的資源
原型:int close(int fd);
參數(shù):fd為前面的sfd,new_fd
返回值:若文件順利關(guān)閉則返回0损谦,發(fā)生錯(cuò)誤時(shí)返回-1
常用實(shí)例:close(new_fd);
close(sfd);
注意:在多進(jìn)程并發(fā)服務(wù)器中岖免,父子進(jìn)程共享套接字,有多少個(gè)進(jìn)程共享該套接字照捡,該套接字就有多少個(gè)引用計(jì)數(shù)颅湘,此時(shí)其中一個(gè)進(jìn)程調(diào)用close只是關(guān)閉了當(dāng)前進(jìn)程的這個(gè)文件描述符,但并沒(méi)有發(fā)生四次揮手栗精,直到這個(gè)套接字的引用計(jì)數(shù)為0時(shí)闯参,才會(huì)發(fā)生四次揮手

1.2.10 sockatmark函數(shù)

功能:判斷sockfd是否處于帶外標(biāo)記瞻鹏,即判斷下一個(gè)讀取的數(shù)據(jù)是否含有帶外數(shù)據(jù),若含有鹿寨,則調(diào)用帶MSG_OOB標(biāo)志的recv來(lái)讀取帶外數(shù)據(jù)
原型:int sockatmark(int sockfd);
參數(shù):fd為前面的sfd,new_fd
返回值:返回1則說(shuō)明下一個(gè)數(shù)據(jù)時(shí)帶外數(shù)據(jù)新博,若返回0,則不是

1.3 tcp客戶端編程

1.3.1 connect函數(shù)

功能:用來(lái)請(qǐng)求連接遠(yuǎn)程服務(wù)器脚草,將參數(shù)sockfd 的socket 連至參數(shù)serv_addr 指定的服務(wù)器IP和端口號(hào)上去赫悄。
原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
參數(shù):sockfdà為前面socket的返回值,即sfd
serv_addrà為結(jié)構(gòu)體指針變量馏慨,存儲(chǔ)著遠(yuǎn)程服務(wù)器的IP與端口號(hào)信息涩蜘。
addrlenà表示結(jié)構(gòu)體變量的長(zhǎng)度
返回值:成功則返回0,失敗返回-1
常用實(shí)例:

struct sockaddr_in seraddr;//請(qǐng)求連接服務(wù)器
memset(&seraddr, 0, sizeof(struct sockaddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8888); //服務(wù)器的端口號(hào)
seraddr.sin_addr.s_addr = inet_addr("192.168.0.101");  //服務(wù)器的ip
if(connect(sfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr)) == -1)
{perror("connect");close(sfd);exit(-1);}

將上面的頭文件以及各個(gè)函數(shù)中的代碼全部拷貝就可以形成一個(gè)完整的例子熏纯,此處省略。
還可以不寫(xiě)客戶端程序粤策,使用telnet遠(yuǎn)程登錄來(lái)檢測(cè)我們的服務(wù)器端程序樟澜。比如我們的服務(wù)器程序在監(jiān)聽(tīng)8888端口,我們可以用telnet 192.168.0.101 8888來(lái)查看服務(wù)端的狀況叮盘。

2. tcp編程實(shí)現(xiàn)

2.1 使用類(lèi)封裝tcp的基本操作

//頭文件 SxTcp.h
#ifndef __SXTCP_H__
#define __SXTCP_H__

#include <stdio.h>

#define TIMEOUT_SEC 1
#define MAX_READ_SIZE BUFSIZ
#define DEFAULT_EPOLL_FD_NUM 1024

//Tcp類(lèi)
class CTcp
{

//構(gòu)造函數(shù)
public:
    CTcp ();
    CTcp (int nSock);
    virtual ~CTcp ();

//重載操作符
public:
    int operator = (int);//賦值
    int operator != (int) const;//不等于操作符
    int operator == (int) const;//等于操作符

//公有成員函數(shù)
public:
    int GetHandle () const;//取出m_nSock
    int Open ();//創(chuàng)建socket
    int Close ();//關(guān)閉監(jiān)聽(tīng)socket
    int Connect (const char *, int) const;//連接(未設(shè)置超時(shí))
    int Bind (const char *, int) const;//綁定
    int Listen (int nNum) const;//監(jiān)聽(tīng)
    int Accept () const;//接受連接
    int Recv(int nFd, char* buf, int nBufLen);//服務(wù)端接收
    int Send(int nFd, char* buf, int nBufLen);//服務(wù)端發(fā)送
    void Close (int nFd);//服務(wù)端關(guān)閉連接socket
    int Send (const void *, int, int = TCP_TIMEOUT) const;//客戶端發(fā)送數(shù)據(jù)
    int Recv (void *, int, int = TCP_TIMEOUT) const;//客戶端接收數(shù)據(jù)

    static const int SOCK_ERROR;
    static const int SOCK_TIMEOUT;
    static const long TCP_TIMEOUT;

//私有成員變量
private:
    int m_nSock;
};

#endif
//sxTcp.cpp
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdarg.h>
#include <arpa/inet.h>
#include <iostream>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <assert.h>
#include <sys/epoll.h>
#include <signal.h>
#include "SxTcp.h"

const int CTcp::SOCK_ERROR = -100;
const int CTcp::SOCK_TIMEOUT = -101;
const long CTcp::TCP_TIMEOUT = 500000;

//構(gòu)造函數(shù)
CTcp::CTcp ()
{
    m_nSock = -1;
}

//構(gòu)造函數(shù)
CTcp::CTcp (int p_iSock)
{
    m_nSock = p_iSock;
}

//析構(gòu)函數(shù)
CTcp::~CTcp ()
{
    Close ();
}

/*賦值
  入?yún)ⅲ簄Sockfd - socket句柄
  出參:賦值后的socket句柄
*/
int CTcp::operator = (int p_iSockfd)
{
    //isfdtype判斷nSockfd是否為指定類(lèi)型秩贰,S_IFSOCK判斷是否為套接字描述符,返回1是柔吼,0不是毒费,-1出錯(cuò)
    assert ((m_nSock == -1) && (p_iSockfd > -1) && (isfdtype (p_iSockfd, S_IFSOCK) == 1));
    m_nSock = p_iSockfd;

    return m_nSock;
}

/*判斷兩個(gè)socket句柄是否不相等
  入?yún)ⅲ簄 - "!="右邊的socket句柄
  出參:1:不相等;0:相等
*/
int CTcp::operator != (int p_iSockfd) const
{
    return (m_nSock != p_iSockfd);
}

/*判斷兩個(gè)socket句柄是否相等
  入?yún)ⅲ簄 - "=="右邊的socket句柄
  出參:1:相等愈魏;0:不相等
*/
int CTcp::operator == (int p_iSockfd) const
{
    return (m_nSock == p_iSockfd);
}

/*取出socket句柄
  入?yún)ⅲ簾o(wú)
  出參:取出的socket句柄
*/
int CTcp::GetHandle () const
{
    return m_nSock;
}

/*創(chuàng)建socket
  入?yún)ⅲ簾o(wú)
  出參:1: 成功 ; 0: 失敗
*/
int CTcp::Open ()
{
    assert (m_nSock == -1);

    //獲取tcp套接字
    m_nSock = socket (AF_INET, SOCK_STREAM, 0);

    return (m_nSock != -1);
}

/*關(guān)閉socket
  入?yún)ⅲ簾o(wú)
  出參:1: 成功 ; 0: 失敗
*/
int CTcp::Close ()
{
    if (m_nSock != -1)
    {
        close (m_nSock);
        m_nSock = -1;
    }
    return 1;
}

/*連接(未設(shè)置超時(shí))觅玻,默認(rèn)為阻塞IO
  入?yún)ⅲ簆Host - IP地址或主機(jī)名
    nPort - 端口
  出參:1: 成功 ; 0: 失敗
*/
int CTcp::Connect (const char *p_szHost, int p_iPort) const
{
    assert ((m_nSock != -1) && p_szHost && (p_iPort > 0));
    struct sockaddr_in addr;
    struct hostent *phe = NULL;

    memset ((void*)&addr, 0, sizeof (addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons (p_iPort);

    if ((addr.sin_addr.s_addr = inet_addr (p_szHost)) == -1)
    {
        if ((phe = gethostbyname (p_szHost)) == NULL)
            return 0;

        memcpy ((char *)&addr.sin_addr, phe->h_addr, phe->h_length);
    }

    return (connect (m_nSock, (struct sockaddr *)&addr, sizeof (addr)) == 0);
}

/*綁定
  入?yún)ⅲ簆IP - IP地址
    nPort - 端口
  出參:1: 成功 ; 0: 失敗
*/
int CTcp::Bind (const char *pIP, int nPort) const
{
    assert ((m_nSock != -1) && (nPort > 0));
    struct sockaddr_in addr;
    struct hostent *phe = NULL;
    int opt=1;

    if (setsockopt (m_nSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) == -1)
    {
        return 0;
    }

    memset (&addr, 0, sizeof (addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons (nPort);

    if (!pIP)
    {

        addr.sin_addr.s_addr = htonl (INADDR_ANY);
    }
    else
    {
        if ((addr.sin_addr.s_addr = inet_addr (pIP)) == -1)
        {
            if ((phe = gethostbyname (pIP)) == NULL)
                return 0;

            memcpy ((char *)&addr.sin_addr, phe->h_addr, phe->h_length);
        }
    }

    return (bind (m_nSock, (struct sockaddr *)&addr, sizeof (addr)) == 0);
}

/*監(jiān)聽(tīng)
  入?yún)ⅲ簄Num - 監(jiān)聽(tīng)數(shù)目
  出參:1: 成功 ; 0: 失敗
*/
int CTcp::Listen (int nNum) const
{
    assert ((m_nSock != -1) && (nNum > 0));

    return (listen (m_nSock, nNum) == 0);
}

/*接受連接
  入?yún)ⅲ簾o(wú)
  出參:其他: 連接套接字句柄 ; -1: 失敗
*/
int CTcp::Accept () const
{
    assert (m_nSock != -1);

    return (accept (m_nSock, (struct sockaddr *)NULL, NULL));
}

/*服務(wù)端接收數(shù)據(jù)
  入?yún)ⅲ簆Buf - 接收緩存
    nCount - 需接收字節(jié)數(shù)
  出參:實(shí)際接收字節(jié)數(shù) ,如果接收失敗培漏,返回負(fù)數(shù)溪厘,如果對(duì)方關(guān)閉,返回0
*/
int CTcp::Recv(int nFd, char* buf, int nBufLen)
{
    assert(nFd != -1 );
    return recv(nFd, buf, nBufLen, 0);
}

/*客戶端接收數(shù)據(jù)
  入?yún)ⅲ簆Buf - 接收緩存
    nCount - 需接收字節(jié)數(shù)
    nMicsec - socket超時(shí)值牌柄,單位:微妙畸悬,缺省:500000微妙
  出參:實(shí)際接收字節(jié)數(shù) 珊佣,如果接收失敗蹋宦,返回負(fù)數(shù),如果對(duì)方關(guān)閉咒锻,返回0
*/
int CTcp::Recv (void *pBuf, int nCount, int nMicsec) const
{
    assert ((m_nSock != -1) && pBuf && (nCount > 0));
    int sn = 0, rn = 0;
    struct timeval tvlTime;
    fd_set rdfdset;

    if (nMicsec >= 0)
    {
        tvlTime.tv_sec = nMicsec / 1000000;
        tvlTime.tv_usec = abs (nMicsec - tvlTime.tv_sec * 1000000);
    }

    FD_ZERO (&rdfdset);
    FD_SET (m_nSock, &rdfdset);

    if (nMicsec > 0)
        sn = select (m_nSock + 1, &rdfdset, NULL, NULL, &tvlTime);
    else
        sn = select (m_nSock + 1, &rdfdset, NULL, NULL, NULL);

    switch (sn)
    {
    case -1:
        return SOCK_ERROR;

    case 0:
        return SOCK_TIMEOUT;
    }

    if ((rn = read (m_nSock, pBuf, nCount)) < 0)
        return SOCK_ERROR;

    return rn;
}



/*服務(wù)端發(fā)送數(shù)據(jù)
入?yún)ⅲ簆Buf - 發(fā)送緩存
nCount - 需發(fā)送字節(jié)數(shù)
出參:實(shí)際發(fā)送字節(jié)數(shù) 冷冗,如果發(fā)送失敗,返回負(fù)數(shù)
*/
int CTcp::Send(int nFd, char* buf, int nBufLen)
{
    assert(nFd != -1 );
    return send(nFd, buf, nBufLen, 0);
}

/*客戶端發(fā)送數(shù)據(jù)
  入?yún)ⅲ簆Buf - 發(fā)送緩存
    nCount - 需發(fā)送字節(jié)數(shù)
    nMicsec - socket超時(shí)值虫碉,單位:微妙贾惦,缺省:500000微妙
  出參:實(shí)際發(fā)送字節(jié)數(shù) ,如果發(fā)送失敗须板,返回負(fù)數(shù)
*/
int CTcp::Send (const void *pBuf, int nCount, int nMicsec) const
{
    assert ((m_nSock != -1) && pBuf && (nCount > 0));
    int sn = 0, wn = 0;
    struct timeval tvlTime;
    fd_set wtfdset;


    if (nMicsec >= 0)
    {
        tvlTime.tv_sec = nMicsec / 1000000;
        tvlTime.tv_usec = abs (nMicsec - tvlTime.tv_sec * 1000000);
    }

    FD_ZERO (&wtfdset);
    FD_SET (m_nSock, &wtfdset);

    if (nMicsec >= 0)
        sn = select (m_nSock + 1, NULL, &wtfdset, NULL, &tvlTime);
    else
        sn = select (m_nSock + 1, NULL, &wtfdset, NULL, NULL);

    switch (sn)
    {
    case -1:
        return SOCK_ERROR;

    case 0:
        return SOCK_TIMEOUT;
    }

    if ((wn = send (m_nSock, pBuf, nCount, 0)) <= 0)
        return SOCK_ERROR;

    return wn;
}


void CTcp::Close (int nFd)
{
    if (nFd != -1 )
    {
        close(nFd);
        nFd = -1;
    }
}

將該類(lèi)編譯成動(dòng)態(tài)庫(kù):

g++ -g -c SxTcp.cpp -fPIC
g++ -g -o libnetwork.so SxTcp.o -shared

2.2 利用類(lèi)CTcp實(shí)現(xiàn)基本服務(wù)端和客戶端

//服務(wù)端TestServer.cpp

//TestServer.cpp
#include <stdio.h>
#include <string.h>
#include "../../network/SxTcp.h"

int main()
{
    CTcp tcp;
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }

    iRet = tcp.Bind("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket bind failed");
        return -1;
    }

    iRet = tcp.Listen(10);
    if (iRet == 0)
    {
        perror("socket listen failed");
        return -1;
    }

    while(1)
    {
        memset(buf, 0, sizeof(buf));

        iFd = tcp.Accept();
        if (iFd == -1 )
        {
            perror("socket accept failed");
            return -1;
        }

        iRet = tcp.Recv(iFd, buf, sizeof(buf));
        if (iRet < 0 )
        {
            perror("recv failed");
            tcp.Close(iFd);
            return -1;
        }
        else if(iRet == 0)
        {
            perror("socket not connect");
            tcp.Close(iFd);
            return -1;
        }

        fprintf(stdout, "recv data is:%s\n", buf);
        memset(buf, 0, sizeof(buf));
        strncpy(buf, "I have redv your data,over!", sizeof(buf)-1);
       
        iRet = tcp.Send(iFd, buf, strlen(buf));
        if (iRet < 0)
        {
            perror("send failed");
            tcp.Close(iFd);
            return -1;
        }
        else if(iRet == 0)
        {
            perror("socket not connect");
            tcp.Close(iFd);
            return -1;
        }
    }

    return 0;
}

//客戶端

//TestClient.cpp
#include <stdio.h>
#include <iostream>
#include <string.h>
#include "../../network/SxTcp.h"
using namespace std;

int main()
{
    CTcp tcp;
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }

    iRet = tcp.Connect("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket connect failed");
        return -1;
    }

    while(1)
    {
        memset(buf, 0, sizeof(buf));
        cout << "please input some string:";
        cin >> buf;
        iRet = tcp.Send(buf, strlen(buf));
        if (iRet < 0 && errno != EAGAIN)
        {
            perror("send failed");
            return -1;
        }
        else if(iRet == 0)
        {
            perror("connect is closed");
            return -1;
        }

        memset(buf, 0, sizeof(buf));
        iRet = tcp.Recv(buf, sizeof(buf));
        if (iRet < 0 && errno != EAGAIN)
        {
            perror("recv failed");
            return -1;
        }
        else if(iRet == 0)
        {
            perror("socket not connect");
            return -1;
        }

        fprintf(stdout, "recv data is:%s\n", buf);
    }

    return 0;
}

分別編譯服務(wù)端和客戶端碰镜,然后發(fā)送數(shù)據(jù),會(huì)發(fā)現(xiàn)客戶端發(fā)送完第一次后习瑰,再第二次循環(huán)中會(huì)報(bào)recv failed绪颖。
這是因?yàn)榉?wù)端阻塞在accept了,沒(méi)辦法第二次接收和發(fā)送數(shù)據(jù)甜奄,那么客戶端超時(shí)以后就會(huì)報(bào)錯(cuò)柠横,返回負(fù)數(shù),導(dǎo)致客戶端退出课兄。
當(dāng)然也可以在服務(wù)端的recv和send外再加一個(gè)循環(huán)牍氛,如下:

//TestServer.cpp
#include <stdio.h>
#include <string.h>
#include "../../network/SxTcp.h"

int main()
{
    CTcp tcp;
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }

    iRet = tcp.Bind("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket bind failed");
        return -1;
    }

    iRet = tcp.Listen(10);
    if (iRet == 0)
    {
        perror("socket listen failed");
        return -1;
    }

    while(1)
    {
        memset(buf, 0, sizeof(buf));

        iFd = tcp.Accept();
        if (iFd == -1 )
        {
            perror("socket accept failed");
            return -1;
        }
        while(1){
            memset(buf, 0, sizeof(buf));
            iRet = tcp.Recv(iFd, buf, sizeof(buf));
            if (iRet < 0 )
            {
                perror("recv failed");
                tcp.Close(iFd);
                return -1;
            }
            else if(iRet == 0)
            {
                perror("socket not connect");
                tcp.Close(iFd);
                return -1;
            }

            fprintf(stdout, "recv data is:%s\n", buf);
            memset(buf, 0, sizeof(buf));
            strncpy(buf, "I have redv your data,over!", sizeof(buf)-1);
           
            iRet = tcp.Send(iFd, buf, strlen(buf));
            if (iRet < 0)
            {
                perror("send failed");
                tcp.Close(iFd);
                return -1;
            }
            else if(iRet == 0)
            {
                perror("socket not connect");
                tcp.Close(iFd);
                return -1;
            }
        }
    }

    return 0;
}

但很顯然,這樣就沒(méi)辦法接收到第二個(gè)連接了烟阐,那么怎么解決呢搬俊?往下面看。

3. 網(wǎng)絡(luò)編程模式

上面的雖然可以實(shí)現(xiàn)多個(gè)客戶端訪問(wèn)蜒茄,但是仍然是阻塞模式(即一個(gè)客戶訪問(wèn)的時(shí)候會(huì)阻塞不讓另外的客戶訪問(wèn))唉擂。解決辦法有三種,分別是多進(jìn)程檀葛、多線程玩祟、異步IO。

3.1 多進(jìn)程

//因?yàn)殚_(kāi)銷(xiāo)比較大屿聋,所以不常用

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "../../network/SxTcp.h"

int main()
{
    CTcp tcp;
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }

    iRet = tcp.Bind("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket bind failed");
        return -1;
    }

    iRet = tcp.Listen(10);
    if (iRet == 0)
    {
        perror("socket listen failed");
        return -1;
    }

    while(1)
    {
        memset(buf, 0, sizeof(buf));

        iFd = tcp.Accept();
        if (iFd == -1 )
        {
            perror("socket accept failed");
            return -1;
        }

        if (fork() == 0)
        {
            while(1){
                memset(buf, 0, sizeof(buf));
                iRet = tcp.Recv(iFd, buf, sizeof(buf));
                if (iRet < 0 )
                {
                    perror("recv failed");
                    tcp.Close(iFd);
                    return -1;
                }
                else if(iRet == 0)
                {
                    perror("socket not connect");
                    tcp.Close(iFd);
                    return -1;
                }

                fprintf(stdout, "recv data is:%s\n", buf);
                memset(buf, 0, sizeof(buf));
                strncpy(buf, "I have redv your data,over!", sizeof(buf)-1);
               
                iRet = tcp.Send(iFd, buf, strlen(buf));
                if (iRet < 0)
                {
                    perror("send failed");
                    tcp.Close(iFd);
                    return -1;
                }
                else if(iRet == 0)
                {
                    perror("socket not connect");
                    tcp.Close(iFd);
                    return -1;
                }
            }
        }
        else
        {
            tcp.Close(iFd);
        }

    }

    return 0;
}

3.2 多線程

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <thread>
#include "../../network/SxTcp.h"

CTcp tcp;

void ReadThread(void* arg)
{
    int *pFd = (int*)arg;
    int iFd = *pFd;

    char buf[128] = {0};
    int iRet = 0;
    while(1){
                memset(buf, 0, sizeof(buf));
                iRet = tcp.Recv(iFd, buf, sizeof(buf));
                if (iRet < 0 )
                {
                    perror("recv failed");
                    tcp.Close(iFd);
                    break;
                }
                else if(iRet == 0)
                {
                    perror("socket not connect");
                    tcp.Close(iFd);
                    break;
                }

                fprintf(stdout, "recv data is:%s\n", buf);
                memset(buf, 0, sizeof(buf));
                strncpy(buf, "I have redv your data,over!", sizeof(buf)-1);
               
                iRet = tcp.Send(iFd, buf, strlen(buf));
                if (iRet < 0)
                {
                    perror("send failed");
                    tcp.Close(iFd);
                    break;
                }
                else if(iRet == 0)
                {
                    perror("socket not connect");
                    tcp.Close(iFd);
                    break;
                }
    }
           
}

int main()
{
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }

    iRet = tcp.Bind("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket bind failed");
        return -1;
    }

    iRet = tcp.Listen(10);
    if (iRet == 0)
    {
        perror("socket listen failed");
        return -1;
    }

    while(1)
    {
        memset(buf, 0, sizeof(buf));

        iFd = tcp.Accept();
        if (iFd == -1 )
        {
            perror("socket accept failed");
            return -1;
        }

        std::thread t_read(ReadThread, (void*)&iFd);
        t_read.detach();

    }

    return 0;
}

3.3 異步IO

異步其實(shí)就是epoll和select模式空扎,可以看另外的兩篇專(zhuān)門(mén)講epoll和select的文章。

4. 使用UDP編程

4.1 UDP協(xié)議

4.1.1 概述

UDP即用戶數(shù)據(jù)報(bào)協(xié)議润讥,它是一種無(wú)連接協(xié)議勺卢,因此不需要像TCP那樣通過(guò)三次握手來(lái)建立一個(gè)連接。同時(shí)象对,一個(gè)UDP應(yīng)用可同時(shí)作為應(yīng)用的客戶或服務(wù)器方黑忱。由于UDP協(xié)議并不需要建立一個(gè)明確的連接,因此建立UDP應(yīng)用要比建立TCP應(yīng)用簡(jiǎn)單得多勒魔。

它比TCP協(xié)議更為高效甫煞,也能更好地解決實(shí)時(shí)性的問(wèn)題。如今冠绢,包括網(wǎng)絡(luò)視頻會(huì)議系統(tǒng)在內(nèi)的眾多的客戶/服務(wù)器模式的網(wǎng)絡(luò)應(yīng)用都使用UDP協(xié)議抚吠。

4.1.2 Udp數(shù)據(jù)包頭格式

源端口占用16bit,表示應(yīng)用程序通過(guò)哪個(gè)端口來(lái)發(fā)送數(shù)據(jù)包弟胀;
目的端口占用16bit楷力,表示數(shù)據(jù)包發(fā)送給對(duì)方應(yīng)用程序的哪個(gè)端口喊式;
長(zhǎng)度占用16bit,表示包含頭部在內(nèi)的udp數(shù)據(jù)包的長(zhǎng)度萧朝;
校驗(yàn)占用16bit岔留,用來(lái)檢查數(shù)據(jù)包是否存在差錯(cuò);

4.1.3 udp基本通信流程及函數(shù)

UDP通信流程圖如下:
服務(wù)端:socket---bind---recvfrom---sendto---close
客戶端:socket----------sendto---recvfrom---close
sendto()函數(shù)原型:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
該函數(shù)比send()函數(shù)多了兩個(gè)參數(shù)检柬,to表示目地機(jī)的IP地址和端口號(hào)信息献联,而tolen常常被賦值為sizeof (struct sockaddr)。sendto 函數(shù)也返回實(shí)際發(fā)送的數(shù)據(jù)字節(jié)長(zhǎng)度或在出現(xiàn)發(fā)送錯(cuò)誤時(shí)返回-1何址。
recvfrom()函數(shù)原型:
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一個(gè)struct sockaddr類(lèi)型的變量里逆,該變量保存連接機(jī)的IP地址及端口號(hào)。fromlen常置為sizeof (struct sockaddr)用爪。當(dāng)recvfrom()返回時(shí)原押,fromlen包含實(shí)際存入from中的數(shù)據(jù)字節(jié)數(shù)。Recvfrom()函數(shù)返回接收到的字節(jié)數(shù)或 當(dāng)出現(xiàn)錯(cuò)誤時(shí)返回-1偎血,并置相應(yīng)的errno班眯。
注意:socket編程還提供了一對(duì)函數(shù)sendmsg/recvmsg用于讀寫(xiě)數(shù)據(jù),該對(duì)函數(shù)既可用于tcp報(bào)文烁巫,也可用于udp報(bào)文,是通用的宠能。

4.2 UDP編程實(shí)現(xiàn)

Example:

//UDP的基本操作
//服務(wù)器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket");
        exit(-1);
    }
 
    struct sockaddr_in saddr;
    bzero(&saddr, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = INADDR_ANY;
    if(bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        close(sfd);
        exit(-1);
    }
 
    char buf[512] = {0};
    while(1)
    {
        struct sockaddr_in fromaddr;
        bzero(&fromaddr, sizeof(fromaddr));
        int fromaddrlen = sizeof(struct sockaddr);
        if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
        {
            perror("recvfrom");
            close(sfd);
            exit(-1);
        }
printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
 
        sendto(sfd, "world", 6, 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
}
 
    close(sfd);
}
//客戶端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket");
        exit(-1);
    }
 
    struct sockaddr_in toaddr;
    bzero(&toaddr, sizeof(toaddr));
    toaddr.sin_family = AF_INET;
    toaddr.sin_port = htons(atoi(argv[2])); //此處的端口號(hào)要跟服務(wù)器一樣
    toaddr.sin_addr.s_addr = inet_addr(argv[1]); //此處為服務(wù)器的ip
    sendto(sfd, "hello", 6, 0, (struct sockaddr*)&toaddr, sizeof(struct sockaddr));
 
    char buf[512] = {0};
    struct sockaddr_in fromaddr;
    bzero(&fromaddr, sizeof(fromaddr));
    int fromaddrlen = sizeof(struct sockaddr);
    if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
    {
        perror("recvfrom");
        close(sfd);
        exit(-1);
    }
printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
 
    close(sfd);
}

Example2:

//UDP發(fā)送文件,先發(fā)文件大小,再發(fā)文件內(nèi)容
//服務(wù)器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket");
        exit(-1);
    }
 
    struct sockaddr_in saddr;
    bzero(&saddr, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = INADDR_ANY;
    if(bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        close(sfd);
        exit(-1);
    }
 
    char buf[512] = {0};
    struct sockaddr_in fromaddr;
    bzero(&fromaddr, sizeof(fromaddr));
    int fromaddrlen = sizeof(struct sockaddr);
    if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
    {
        perror("recvfrom");
        close(sfd);
        exit(-1);
    }
    printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
    FILE* fp = fopen("1.txt","rb");
    struct stat st;  //用于獲取文件內(nèi)容的大小
    stat("1.txt", &st);
    int filelen = st.st_size;
    sendto(sfd, (void*)&filelen, sizeof(int), 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
    while(!feof(fp))   //表示沒(méi)有到文件尾
    {
        int len = fread(buf,1,sizeof(buf),fp);
        sendto(sfd, buf, len, 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
}
 
    close(sfd);
}
//客戶端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 512
int main(int argc, char* argv[])
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket");
        exit(-1);
    }
 
    struct sockaddr_in toaddr;
    bzero(&toaddr, sizeof(toaddr));
    toaddr.sin_family = AF_INET;
    toaddr.sin_port = htons(atoi(argv[2]));
    toaddr.sin_addr.s_addr = inet_addr(argv[1]);
    sendto(sfd, "hello", 6, 0, (struct sockaddr*)&toaddr, sizeof(struct sockaddr));
 
    char buf[BUFSIZE] = {0};
    struct sockaddr_in fromaddr;
    bzero(&fromaddr, sizeof(fromaddr));
    int fromaddrlen = sizeof(struct sockaddr);
    int filelen = 0;   //用于保存文件長(zhǎng)度
    FILE* fp = fopen("2.txt","w+b");
//接收文件的長(zhǎng)度
recvfrom(sfd, (void*)&filelen, sizeof(int), 0, (struct sockaddr*)&fromaddr, &fromaddrlen);
    printf("the length of file is %d\n",filelen);
    printf("Create a new file!\n");
    printf("begin to reveive file content!\n");
    //接收文件的內(nèi)容
while(1)
    {
        int len = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen);
        if(len < BUFSIZE)
//如果接收的長(zhǎng)度小于BUFSIZE亚隙,則表示最后一次接收,此時(shí)要用break退出循環(huán)
        {
            fwrite(buf,sizeof(char),len,fp);
            break;
        }
        fwrite(buf,sizeof(char),len,fp);
    }
    printf("receive file finished!\n");
    close(sfd);
}

5. 協(xié)議的選擇

  • 對(duì)數(shù)據(jù)要求高可靠性的應(yīng)用需選擇TCP協(xié)議违崇,如驗(yàn)證阿弃、密碼字段的傳送都是不允許出錯(cuò)的,而對(duì)數(shù)據(jù)的可靠性要求不那么高的應(yīng)用可選擇UDP傳送羞延;
  • TCP協(xié)議在傳送過(guò)程中要使用三次握手渣淳、重傳確認(rèn)等手段來(lái)保證數(shù)據(jù)傳輸?shù)目煽啃浴J褂肨CP協(xié)議會(huì)有較大的時(shí)延伴箩,因此不適合對(duì)實(shí)時(shí)性要求較高的應(yīng)用入愧,如VOIP、視頻監(jiān)控等嗤谚。相反棺蛛,UDP協(xié)議則在這些應(yīng)用中能發(fā)揮很好的作用;
  • 由于TCP協(xié)議的提出主要是解決網(wǎng)絡(luò)的可靠性問(wèn)題巩步,它通過(guò)各種機(jī)制來(lái)減少錯(cuò)誤發(fā)生的概率旁赊。因此,在網(wǎng)絡(luò)狀況不是很好的情況下需選用TCP協(xié)議(如在廣域網(wǎng)等情況)椅野,但是若在網(wǎng)絡(luò)狀況很好的情況下(如局域網(wǎng)等)就不需要再采用TCP協(xié)議终畅,而建議選擇UDP協(xié)議來(lái)減少網(wǎng)絡(luò)負(fù)荷籍胯;

本人在簡(jiǎn)書(shū)上寫(xiě)的內(nèi)容均為本人原創(chuàng),轉(zhuǎn)載需經(jīng)本人同意离福,歡迎轉(zhuǎn)載分享杖狼,請(qǐng)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末术徊,一起剝皮案震驚了整個(gè)濱河市本刽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赠涮,老刑警劉巖子寓,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異笋除,居然都是意外死亡斜友,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)垃它,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鲜屏,“玉大人,你說(shuō)我怎么就攤上這事国拇÷迨罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵酱吝,是天一觀的道長(zhǎng)也殖。 經(jīng)常有香客問(wèn)我,道長(zhǎng)务热,這世上最難降的妖魔是什么忆嗜? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮崎岂,結(jié)果婚禮上捆毫,老公的妹妹穿的比我還像新娘。我一直安慰自己冲甘,他們只是感情好绩卤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著江醇,像睡著了一般省艳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫁审,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天跋炕,我揣著相機(jī)與錄音,去河邊找鬼律适。 笑死辐烂,一個(gè)胖子當(dāng)著我的面吹牛遏插,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纠修,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼胳嘲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了扣草?” 一聲冷哼從身側(cè)響起了牛,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辰妙,沒(méi)想到半個(gè)月后鹰祸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡密浑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年蛙婴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尔破。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡街图,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懒构,到底是詐尸還是另有隱情餐济,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布胆剧,位于F島的核電站絮姆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赞赖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一冤灾、第九天 我趴在偏房一處隱蔽的房頂上張望前域。 院中可真熱鬧,春花似錦韵吨、人聲如沸匿垄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)椿疗。三九已至,卻和暖如春糠悼,著一層夾襖步出監(jiān)牢的瞬間届榄,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工倔喂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铝条,地道東北人靖苇。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像班缰,于是被迫代替她去往敵國(guó)和親贤壁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 一埠忘、概述 TCP(傳輸控制協(xié)議)和UDP(用戶數(shù)據(jù)報(bào)協(xié)議)是網(wǎng)絡(luò)體系結(jié)構(gòu)TCP/IP模型中傳輸層一層中的兩個(gè)不同的...
    小葉大孟閱讀 688評(píng)論 0 1
  • TCP和UDP協(xié)議屬于傳輸層協(xié)議,其中TCP提供IP環(huán)境下的數(shù)據(jù)可靠傳輸莹妒,它提供的服務(wù)包括數(shù)據(jù)流傳送名船、可靠性、有效...
    肆意咯咯咯閱讀 860評(píng)論 1 1
  • TCP和UDP編程區(qū)別 TCP編程的服務(wù)器端一般步驟是: 1动羽、創(chuàng)建一個(gè)socket包帚,用函數(shù)socket(); 2运吓、...
    nit小星星閱讀 436評(píng)論 0 0
  • TCP和UDP區(qū)別 TCP和UDP編程區(qū)別 TCP編程的服務(wù)器端一般步驟是:1渴邦、創(chuàng)建一個(gè)socket,用函數(shù)soc...
    Flutter求學(xué)者閱讀 1,588評(píng)論 0 16
  • 1.1 網(wǎng)絡(luò)常識(shí) 1)網(wǎng)絡(luò)的7層網(wǎng)絡(luò)協(xié)議 ISO按照邏輯劃分出來(lái)7層網(wǎng)絡(luò)協(xié)議 應(yīng)用層:和應(yīng)用程序打交道的拘哨,進(jìn)行數(shù)據(jù)...
    vera姐姐閱讀 479評(píng)論 0 1