包尾加
编程实践
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
与read相比,只能用于套接字文件描写符,而且多了1个flags
Flags经常使用取值:
MSG_OOB(紧急指针,带外数据)
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(可以读数据,不从缓存区中读走,利用此特点可以方便的实现按行读取数据;1个1个字符的读,方法不好;屡次调用系统调用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.原客户端代码分析
....
//从键盘输入数据:调用fgets函数,行尾的/n是默许自带的,请参看下图gdb调试的截图
while (fgets(sendBuf.m_text,sizeof(sendBuf.m_text),stdin) != NULL)
{
//保存的是真实报文的长度
sendBuf.m_length = strlen(sendBuf.m_text);
//向server发送数据....+4的缘由:需要添加报首的4个字节报头的长度
if (writen(sockfd,&sendBuf,sendBuf.m_length+4) == ⑴)
{
err_exit("write socket error");
}
.....
2.readline函数实现及解析
//只是查看1下网络中的数据,其实不是将之真正取走:MSG_PEEK
ssize_t recv_peek(int fd, void *buf, size_t count)
{
int nRead = 0;
//如果读取网络数据出错,则继续读取
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)
{
//查看缓冲区中的数据,其实不真正取走
int nTestRead = recv_peek(fd,pBuf,nLeft);
//检测这次读来的数据中是不是包括'
';
//如果有,则将之全部读取出来
for (int i = 0; i < nTestRead; ++i)
{
if (pBuf[i] == '
')
{
//真实的从缓冲区中将数据取走
if (readn(fd,pBuf,i+1) != i+1)
{
err_exit("readn error");
}
else
{
return i + 1;
}
}
}
//如果这次读的缓冲区中没有'
'
//如果读超了:读道德数目大于1行最大数,则做异常处理
if (nTestRead > nLeft)
{
exit(EXIT_FAILURE);
}
nLeft -= nTestRead; //若缓冲区没有'
',则将剩余的数据读走
if (readn(fd,pBuf,nTestRead) != nTestRead)
{
exit(EXIT_FAILURE);
}
pBuf += nTestRead;
}
return ⑴;
}
3.server端完全代码及解析
#include "commen.h"
//echo
服务器readline版
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if (sockfd == ⑴)
{
err_exit("socket error");
}
//添加地址复用
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; //绑定本机的任意1个IP地址
if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == ⑴)
{
err_exit("bind error");
}
//启动监听套接字
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个客户端连接进来,就fork1个子进程,
//相应的业务处理由子进程完成,父进程继续监听
pid_t pid = fork();
if (pid == ⑴)
{
close(sockfd);
close(peerSockfd);
err_exit("fork error");
}
else if (pid == 0) //子进程,处理业务
{
close(sockfd); //子进程关闭监听套接字,由于子进程不负责监听凭务
char recvBuf[BUFSIZ];
ssize_t readCount = 0;
while (true)
{
memset(recvBuf,0,sizeof(recvBuf));
//读取1行数据(会根据数据流中的
而终止读取)
if ((readCount = readline(peerSockfd,recvBuf,sizeof(recvBuf))) == ⑴)
{
err_exit("readn error");
}
else if (readCount == 0)
{
peerClosePrint("client connect closed");
}
//将整体报文回写回客户端
if (writen(peerSockfd,recvBuf,strlen(recvBuf)) == ⑴)
{
err_exit("writen error");
}
recvBuf[readCount] = 0;
//写至终端
fputs(recvBuf,stdout);
}
}
else if (pid > 0) //父进程
{
close(peerSockfd);
}
}
close(sockfd);
return 0;
}
4.新版client端完全代码实现及解析
#include "commen.h"
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if (sockfd == ⑴)
{
err_exit("socket error");
}
//填写好
服务器地址及其端口号
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];
//从键盘输入数据:调用fgets函数,行尾的/n是默许自带的,请参看下图gdb调试的截图
while (fgets(sendBuf,sizeof(sendBuf),stdin) != NULL)
{
//向server发送数据(会自动附带
)
if (writen(sockfd,sendBuf,strlen(sendBuf)) == ⑴)
{
err_exit("write socket error");
}
//从server端接收1行数据
if ((readCount = readline(sockfd,recvBuf,sizeof(recvBuf))) == ⑴)
{
err_exit("read socket error");
}
else if (readCount == 0)
{
peerClosePrint("client connect closed");
}
recvBuf[readCount] = 0;
//将其回写到终端
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点东西都没读
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点东西都没写
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下网络中的数据,其实不是将之真正取走:MSG_PEEK
ssize_t recv_peek(int fd, void *buf, size_t count)
{
int nRead = 0;
//如果读取网络数据出错,则继续读取
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)
{
//查看缓冲区中的数据,其实不真正取走
int nTestRead = recv_peek(fd,pBuf,nLeft);
//检测这次读来的数据中是不是包括'
';
//如果有,则将之全部读取出来
for (int i = 0; i < nTestRead; ++i)
{
if (pBuf[i] == '
')
{
//真实的从缓冲区中将数据取走
if (readn(fd,pBuf,i+1) != i+1)
{
err_exit("readn error");
}
else
{
return i + 1;
}
}
}
//如果这次读的缓冲区中没有'
'
//如果读超了:读道德数目大于1行最大数,则做异常处理
if (nTestRead > nLeft)
{
exit(EXIT_FAILURE);
}
nLeft -= nTestRead; //若缓冲区没有'
',则将剩余的数据读走
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