Socket編程實(shí)踐(6) --TCP服務(wù)器常見(jiàn)問(wèn)題(1)
來(lái)源:程序員人生 發(fā)布時(shí)間:2014-12-17 08:21:14 閱讀次數(shù):2805次
流協(xié)議與粘包
粘包的表現(xiàn)
Host A 發(fā)送數(shù)據(jù)給 Host B; 而Host B 接收數(shù)據(jù)的方式不肯定

粘包產(chǎn)生的緣由 
說(shuō)明
TCP | 字節(jié)流,無(wú)邊界 | 對(duì)等方,1次讀操作,不能保證完全把消息讀完 |
UDP | 數(shù)據(jù)報(bào),有邊界 | 對(duì)方接受數(shù)據(jù)包的個(gè)數(shù)是不肯定的 |
產(chǎn)生粘包問(wèn)題的緣由分析
1、SQ_SNDBUF 套接字本身有緩沖區(qū) (發(fā)送緩沖區(qū)、接受緩沖區(qū))
2、tcp傳送的端 mss大小限制
3、鏈路層也有MTU大小限制,如果數(shù)據(jù)包大于>MTU要在IP層進(jìn)行分片,致使消息分割。
4、tcp的流量控制和堵塞控制,也可能致使粘包
5、tcp延遲發(fā)送機(jī)制等
結(jié)論:tcp/ip協(xié)議,在傳輸層沒(méi)有處理粘包問(wèn)題。
粘包解決方案(本質(zhì)上是要在利用層保護(hù)消息與消息的邊界)
定長(zhǎng)包
包尾加
(ftp)
包頭加上包體長(zhǎng)度(以下)
更復(fù)雜的利用層協(xié)議
編程實(shí)踐-readn && writen
管道,FIFO和某些裝備(特別是終端和網(wǎng)絡(luò))有以下兩種性質(zhì):
1)1次read操作所返回的數(shù)據(jù)可能少于所要求的數(shù)據(jù),即便還沒(méi)到達(dá)文件尾端也可能這樣,但這不是1個(gè)毛病,應(yīng)當(dāng)繼續(xù)讀該裝備;
2)1次write操作的返回值也可能少于指定輸入的字節(jié)數(shù).這多是由于某個(gè)因素釀成的,如:內(nèi)核緩沖區(qū)滿...但這也不是1個(gè)毛病,應(yīng)當(dāng)繼續(xù)寫(xiě)余下的數(shù)據(jù)(通常,只有非阻塞描寫(xiě)符,或捕捉到1個(gè)信號(hào)時(shí),才產(chǎn)生這類write的中途返回)
在讀寫(xiě)磁盤(pán)文件時(shí)從未見(jiàn)到過(guò)這類情況,除非是文件系統(tǒng)用完了空間,或接近了配額限制,不能將所要求寫(xiě)的數(shù)據(jù)全部寫(xiě)出!
通常,在讀,寫(xiě)1個(gè)網(wǎng)絡(luò)裝備,管道或終端時(shí),需要斟酌這些特性.因而,我們就有了下面的這兩個(gè)函數(shù):readn和writen,功能分別是讀寫(xiě)指定的N字節(jié)數(shù)據(jù),并處理返回值可能小于要求值的情況:
ssize_t readnint fd, void *buf, size_t count);
ssize_t writen(int fd, const void *buf, size_t count);
返回值:
讀寫(xiě)的字節(jié)數(shù);若出錯(cuò),返回⑴
實(shí)現(xiàn):
這兩個(gè)函數(shù)只是按需屢次調(diào)用read和write系統(tǒng)調(diào)用直至讀寫(xiě)了N個(gè)數(shù)據(jù)
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nLeft = count;
ssize_t nRead = 0;
char *ptr = static_cast<char *>(buf);
while (nLeft > 0)
{
if ((nRead = read(fd,ptr,nLeft)) < 0)
{
//1點(diǎn)東西都沒(méi)讀
if (nLeft == count)
{
return ⑴; //error
}
else
{
break; //error, return amount read so far
}
}
else if (nRead == 0)
{
break; //EOF
}
nLeft -= nRead;
ptr += nRead;
}
return count - nLeft;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nLeft = count;
ssize_t nWritten;
const char *ptr = static_cast<const char *>(buf);
while (nLeft > 0)
{
if ((nWritten = write(fd,ptr,nLeft)) < 0)
{
//1點(diǎn)東西都沒(méi)寫(xiě)
if (nLeft == count)
{
return ⑴; //error
}
else
{
break; //error, return amount write so far
}
}
else if (nWritten == 0)
{
break; //EOF
}
nLeft -= nWritten;
ptr += nWritten;
}
return count - nWritten;
}
報(bào)頭加上報(bào)文長(zhǎng)度編程實(shí)踐
報(bào)文結(jié)構(gòu):
struct TransStruct
{
int m_length; //報(bào)頭:保存數(shù)據(jù)m_text的真實(shí)數(shù)據(jù)長(zhǎng)度
char m_text[BUFSIZ]; //報(bào)文:保存真正要發(fā)送的數(shù)據(jù)
};
發(fā)報(bào)文時(shí):前4個(gè)字節(jié)長(zhǎng)度+報(bào)文
收?qǐng)?bào)文時(shí):先讀前4個(gè)字節(jié),求出長(zhǎng)度;根據(jù)長(zhǎng)度讀數(shù)據(jù)。
//server端完全代碼及解析
#include "commen.h"
//echo
服務(wù)器writen,readn 版
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if (sockfd == ⑴)
{
err_exit("socket error");
}
//添加地址復(fù)用
int optval = 1;
if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == ⑴)
{
err_exit("setsockopt SO_REUSEADDR error");
}
//綁定
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8002);
serverAddr.sin_addr.s_addr = INADDR_ANY; //綁定本機(jī)的任意1個(gè)IP地址
if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == ⑴)
{
err_exit("bind error");
}
//啟動(dòng)監(jiān)聽(tīng)套接字
if (listen(sockfd,SOMAXCONN) == ⑴)
{
err_exit("listen error");
}
struct sockaddr_in peerAddr;
socklen_t peerLen = sizeof(peerAddr);
while (true)
{
//接受鏈接
int peerSockfd = accept(sockfd, (struct sockaddr *)&peerAddr,&peerLen);
if (peerSockfd == ⑴)
{
err_exit("accept error");
}
//打印客戶信息
cout << "Client:" << endl;
cout << " sin_port: " << ntohs(peerAddr.sin_port) << endl;
cout << " sin_addr: " << inet_ntoa(peerAddr.sin_addr) << endl;
cout << " socket: " << peerSockfd << endl;
//每有1個(gè)客戶端連接進(jìn)來(lái),就fork1個(gè)子進(jìn)程,
//相應(yīng)的業(yè)務(wù)處理由子進(jìn)程完成,父進(jìn)程繼續(xù)監(jiān)聽(tīng)
pid_t pid = fork();
if (pid == ⑴)
{
close(sockfd);
close(peerSockfd);
err_exit("fork error");
}
else if (pid == 0) //子進(jìn)程,處理業(yè)務(wù)
{
close(sockfd); //子進(jìn)程關(guān)閉監(jiān)聽(tīng)套接字,由于子進(jìn)程不負(fù)責(zé)監(jiān)聽(tīng)?wèi){務(wù)
struct TransStruct recvBuf;
ssize_t readCount = 0;
while (true)
{
memset(&recvBuf,0,sizeof(recvBuf));
//首先,從客戶端讀取報(bào)頭長(zhǎng)度
if ((readCount = readn(peerSockfd,&(recvBuf.m_length),4)) == ⑴)
{
err_exit("readn error");
}
else if (readCount == 0) //如果鏈接關(guān)閉
{
peerClosePrint("client connect closed");
}
//根據(jù)報(bào)文實(shí)際長(zhǎng)度,讀取數(shù)據(jù)
if ((readCount = readn(peerSockfd,&(recvBuf.m_text),recvBuf.m_length)) == ⑴)
{
err_exit("readn error");
}
else if (readCount == 0)
{
peerClosePrint("client connect closed");
}
//將整體報(bào)文回寫(xiě)回客戶端
if (writen(peerSockfd,&recvBuf,recvBuf.m_length+4) == ⑴)
{
err_exit("writen error");
}
recvBuf.m_text[recvBuf.m_length] = 0;
//寫(xiě)至終端
fputs(recvBuf.m_text,stdout);
}
}
else if (pid > 0) //父進(jìn)程
{
close(peerSockfd);
}
}
close(sockfd);
return 0;
}
//client端完全代碼實(shí)現(xiàn)及解析
#include "commen.h"
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if (sockfd == ⑴)
{
err_exit("socket error");
}
//填寫(xiě)好
服務(wù)器地址及其端口號(hào)
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8002);
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == ⑴)
{
err_exit("connect error");
}
int readCount = 0;
struct TransStruct sendBuf;
struct TransStruct recvBuf;
//從鍵盤(pán)輸入數(shù)據(jù)
while (fgets(sendBuf.m_text,sizeof(sendBuf.m_text),stdin) != NULL)
{
//保存的是真實(shí)報(bào)文的長(zhǎng)度
sendBuf.m_length = strlen(sendBuf.m_text);
//向server發(fā)送數(shù)據(jù)....+4的緣由:需要添加報(bào)首的4個(gè)字節(jié)報(bào)頭的長(zhǎng)度
if (writen(sockfd,&sendBuf,sendBuf.m_length+4) == ⑴)
{
err_exit("write socket error");
}
//首先,從server端接收將要發(fā)送的數(shù)據(jù)報(bào)的長(zhǎng)度
if ((readCount = readn(sockfd,&(recvBuf.m_length),4)) == ⑴)
{
err_exit("read socket error");
}
else if (readCount == 0)
{
peerClosePrint("client connect closed");
}
//然后,根據(jù)從server端讀來(lái)的報(bào)文長(zhǎng)度,讀取報(bào)文
if ((readCount = readn(sockfd,&(recvBuf.m_text),recvBuf.m_length)) == ⑴)
{
err_exit("read socket error");
}
else if (readCount == 0)
{
peerClosePrint("client connect closed");
}
recvBuf.m_text[recvBuf.m_length] = 0;
//將其回寫(xiě)到終端
fputs(recvBuf.m_text,stdout);
memset(&sendBuf,0,sizeof(sendBuf));
memset(&recvBuf,0,sizeof(recvBuf));
}
close(sockfd);
return 0;
}
附-commen.h完全代碼及解析
#ifndef COMMEN_H_INCLUDED
#define COMMEN_H_INCLUDED
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
//報(bào)文結(jié)構(gòu)
struct TransStruct
{
int m_length; //報(bào)頭:保存數(shù)據(jù)m_text的真實(shí)數(shù)據(jù)長(zhǎng)度
char m_text[BUFSIZ]; //報(bào)文:保存真正要發(fā)送的數(shù)據(jù)
};
//出錯(cuò)退出
void err_exit(std::string str)
{
perror(str.c_str());
exit(EXIT_FAILURE);
}
//對(duì)端關(guān)閉鏈接退出
void peerClosePrint(std::string str = "peer connect closed")
{
cout << str << endl;
_exit(0);
}
//信號(hào)捕獲函數(shù):上1篇博客中的代碼需要使用的
void onSignal(int signalNumber)
{
switch (signalNumber)
{
case SIGUSR1:
cout << "child receive SIGUSR1" << signalNumber << endl;
_exit(0);
case SIGUSR2:
cout << "parent receive SIGUSR2: " << signalNumber << endl;
_exit(0);
default:
cout << "RECV OTHRER SIGNAL" << endl;
}
}
//經(jīng)典的readn函數(shù)(來(lái)源:APUE)
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nLeft = count;
ssize_t nRead = 0;
char *ptr = static_cast<char *>(buf);
while (nLeft > 0)
{
if ((nRead = read(fd,ptr,nLeft)) < 0)
{
//1點(diǎn)東西都沒(méi)讀
if (nLeft == count)
{
return ⑴; //error
}
else
{
break; //error, return amount read so far
}
}
else if (nRead == 0)
{
break; //EOF
}
nLeft -= nRead;
ptr += nRead;
}
return count - nLeft;
}
//經(jīng)典的writen函數(shù)(來(lái)源:APUE)
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nLeft = count;
ssize_t nWritten;
const char *ptr = static_cast<const char *>(buf);
while (nLeft > 0)
{
if ((nWritten = write(fd,ptr,nLeft)) < 0)
{
//1點(diǎn)東西都沒(méi)寫(xiě)
if (nLeft == count)
{
return ⑴; //error
}
else
{
break; //error, return amount write so far
}
}
else if (nWritten == 0)
{
break; //EOF
}
nLeft -= nWritten;
ptr += nWritten;
}
return count - nWritten;
}
#endif // COMMEN_H_INCLUDED
生活不易,碼農(nóng)辛苦
如果您覺(jué)得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)