Socket編程實(shí)踐(7) --TCP服務(wù)器常見(jiàn)問(wèn)題(2)
來(lái)源:程序員人生 發(fā)布時(shí)間:2014-12-19 08:21:43 閱讀次數(shù):3221次
包尾加
編程實(shí)踐
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
與read相比,只能用于套接字文件描寫(xiě)符,而且多了1個(gè)flags
Flags經(jīng)常使用取值:
MSG_OOB(緊急指針,帶外數(shù)據(jù))
This flag requests receipt of out-of-band data that would not be received in the normal data stream. Some protocols place expedited data at the head of the normal data queue, and thus this flag cannot be used with such protocols.
MSG_PEEK(可以讀數(shù)據(jù),不從緩存區(qū)中讀走,利用此特點(diǎn)可以方便的實(shí)現(xiàn)按行讀取數(shù)據(jù);1個(gè)1個(gè)字符的讀,方法不好;屢次調(diào)用系統(tǒng)調(diào)用read方法)
This flag causes the receive operation to return data from the beginning of the receive queue without removing that data from the queue. Thus, a subsequent receive call will return the same data.
1.原客戶端代碼分析
....
//從鍵盤(pán)輸入數(shù)據(jù):調(diào)用fgets函數(shù),行尾的/n是默許自帶的,請(qǐng)參看下圖gdb調(diào)試的截圖
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");
}
.....

2.readline函數(shù)實(shí)現(xiàn)及解析
//只是查看1下網(wǎng)絡(luò)中的數(shù)據(jù),其實(shí)不是將之真正取走:MSG_PEEK
ssize_t recv_peek(int fd, void *buf, size_t count)
{
int nRead = 0;
//如果讀取網(wǎng)絡(luò)數(shù)據(jù)出錯(cuò),則繼續(xù)讀取
while ((nRead = recv(fd,buf,count,MSG_PEEK)) == ⑴);
return nRead;
}
ssize_t readline(int fd, void *buf, size_t maxline)
{
char *pBuf = (char *)buf;
int nLeft = maxline;
while (true)
{
//查看緩沖區(qū)中的數(shù)據(jù),其實(shí)不真正取走
int nTestRead = recv_peek(fd,pBuf,nLeft);
//檢測(cè)這次讀來(lái)的數(shù)據(jù)中是不是包括'
';
//如果有,則將之全部讀取出來(lái)
for (int i = 0; i < nTestRead; ++i)
{
if (pBuf[i] == '
')
{
//真實(shí)的從緩沖區(qū)中將數(shù)據(jù)取走
if (readn(fd,pBuf,i+1) != i+1)
{
err_exit("readn error");
}
else
{
return i + 1;
}
}
}
//如果這次讀的緩沖區(qū)中沒(méi)有'
'
//如果讀超了:讀道德數(shù)目大于1行最大數(shù),則做異常處理
if (nTestRead > nLeft)
{
exit(EXIT_FAILURE);
}
nLeft -= nTestRead; //若緩沖區(qū)沒(méi)有'
',則將剩余的數(shù)據(jù)讀走
if (readn(fd,pBuf,nTestRead) != nTestRead)
{
exit(EXIT_FAILURE);
}
pBuf += nTestRead;
}
return ⑴;
}
3.server端完全代碼及解析
#include "commen.h"
//echo
服務(wù)器readline版
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ù)
char recvBuf[BUFSIZ];
ssize_t readCount = 0;
while (true)
{
memset(recvBuf,0,sizeof(recvBuf));
//讀取1行數(shù)據(jù)(會(huì)根據(jù)數(shù)據(jù)流中的
而終止讀取)
if ((readCount = readline(peerSockfd,recvBuf,sizeof(recvBuf))) == ⑴)
{
err_exit("readn error");
}
else if (readCount == 0)
{
peerClosePrint("client connect closed");
}
//將整體報(bào)文回寫(xiě)回客戶端
if (writen(peerSockfd,recvBuf,strlen(recvBuf)) == ⑴)
{
err_exit("writen error");
}
recvBuf[readCount] = 0;
//寫(xiě)至終端
fputs(recvBuf,stdout);
}
}
else if (pid > 0) //父進(jìn)程
{
close(peerSockfd);
}
}
close(sockfd);
return 0;
}
4.新版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;
char sendBuf[BUFSIZ];
char recvBuf[BUFSIZ];
//從鍵盤(pán)輸入數(shù)據(jù):調(diào)用fgets函數(shù),行尾的/n是默許自帶的,請(qǐng)參看下圖gdb調(diào)試的截圖
while (fgets(sendBuf,sizeof(sendBuf),stdin) != NULL)
{
//向server發(fā)送數(shù)據(jù)(會(huì)自動(dòng)附帶
)
if (writen(sockfd,sendBuf,strlen(sendBuf)) == ⑴)
{
err_exit("write socket error");
}
//從server端接收1行數(shù)據(jù)
if ((readCount = readline(sockfd,recvBuf,sizeof(recvBuf))) == ⑴)
{
err_exit("read socket error");
}
else if (readCount == 0)
{
peerClosePrint("client connect closed");
}
recvBuf[readCount] = 0;
//將其回寫(xiě)到終端
fputs(recvBuf,stdout);
memset(sendBuf,0,sizeof(sendBuf));
memset(recvBuf,0,sizeof(recvBuf));
}
close(sockfd);
return 0;
}
附1-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;
void err_exit(std::string str)
{
perror(str.c_str());
exit(EXIT_FAILURE);
}
void peerClosePrint(std::string str = "peer connect closed")
{
cout << str << endl;
_exit(0);
}
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;
}
//只是查看1下網(wǎng)絡(luò)中的數(shù)據(jù),其實(shí)不是將之真正取走:MSG_PEEK
ssize_t recv_peek(int fd, void *buf, size_t count)
{
int nRead = 0;
//如果讀取網(wǎng)絡(luò)數(shù)據(jù)出錯(cuò),則繼續(xù)讀取
while ((nRead = recv(fd,buf,count,MSG_PEEK)) == ⑴);
return nRead;
}
ssize_t readline(int fd, void *buf, size_t maxline)
{
char *pBuf = (char *)buf;
int nLeft = maxline;
while (true)
{
//查看緩沖區(qū)中的數(shù)據(jù),其實(shí)不真正取走
int nTestRead = recv_peek(fd,pBuf,nLeft);
//檢測(cè)這次讀來(lái)的數(shù)據(jù)中是不是包括'
';
//如果有,則將之全部讀取出來(lái)
for (int i = 0; i < nTestRead; ++i)
{
if (pBuf[i] == '
')
{
//真實(shí)的從緩沖區(qū)中將數(shù)據(jù)取走
if (readn(fd,pBuf,i+1) != i+1)
{
err_exit("readn error");
}
else
{
return i + 1;
}
}
}
//如果這次讀的緩沖區(qū)中沒(méi)有'
'
//如果讀超了:讀道德數(shù)目大于1行最大數(shù),則做異常處理
if (nTestRead > nLeft)
{
exit(EXIT_FAILURE);
}
nLeft -= nTestRead; //若緩沖區(qū)沒(méi)有'
',則將剩余的數(shù)據(jù)讀走
if (readn(fd,pBuf,nTestRead) != nTestRead)
{
exit(EXIT_FAILURE);
}
pBuf += nTestRead;
}
return ⑴;
}
#endif // COMMEN_H_INCLUDED
附2-Mafile文件
CC = g++
CPPFLAGS = -Wall -g -pthread
BIN = server client
SOURCES = $(BIN.=.cpp)
.PHONY: clean all
all: $(BIN)
$(BIN): $(SOURCES)
clean:
-rm -rf $(BIN) bin/ obj/ core
生活不易,碼農(nóng)辛苦
如果您覺(jué)得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)