本文主要总结关于TCP网络编程以及实现;
一.TCP的网络编程:
传输层基于TCP协议进行网络通信编程:
1.传输流程:
服务端:
(1)创建套接字;
(2)绑定地址信息;
(3)开始监听:告诉系统可以接收客户端的连接请求;(这个过程中若有新的客户端连接请求到来,系统就会为这个客户端新建一个socket提供一对一服务;)
监听的接口:
int listen(int sockfd,int backlog);
//其中backlog决定了内核中已完成对列的最大节点数量;为新客户端创建的socket在完成连接后都会放置到对列中;backlog决定了同一时间最大并发连接数;
listen在内核中创建两个对列:已完成连接对列,未完成连接对列;
socket为新的客户端在操作系统中重新建立一个新的socket;放在未连接对列中,当新建socket连接建立完成,就会将socket放到已完成连接对列中;当已完成对列的最大节点数量为backlog时,就不会新建socket,后来的客户端都将被丢弃;
(4)获取已经连接成功的客户端socket;=====>newsockfd
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
//从已完成对列中获取新建的客户端socket;获取之后就可以通过这个新建的socket与指定客户端进行通信;
客户端:
(1)创建套接字;
(2)绑定地址信息;(客户端不推荐手动绑定地址信息)
(3)向服务端发起连接请求;(客户端发起的连接请求必须在服务端建立连接之后,该请求为SYN请求,然后服务端也要向客户端进行回复并发送连接请求ACK+SYN(分为两部分:第一步是对客户端的请求做一个回复;第二步:为保证安全也同时给客户端发送连接请求);客户端再次进行回复;)
int connect (int sockfd,const,struct sockaddr *addr,socklen_t addrlen);
sockfd:套接字描述符;
addr:服务端的地址信息;
addrlen:地址信息的长度
(4)发送数据;
ssize_t send (int sockfd,const void *buf,size_t len, int flags);
sockfd:套接字描述符;
buf:要发送的数据;
len:要发送的数据长度;
flags:标志位;
(5)接收数据:
ssize_t recv(int socket,void *buf,size_t len,int flags);
(6)关闭套接字:
close(fd);
原理图如下:

xshell通过22号端口向服务器发送请求;此时sshd监听22号端口,使xshell进行连接;(sshd是一个程序,它的内核中有一个socket结构体,结构体中对应了绑定信息,网卡拿到发往22号端口的信息都放到socket结构体中;在socket中一共有两个缓冲区:一个接收缓冲区,一个发送缓冲区)监听socket刚开始的时候监听的是22号端口,当有新的连接请求进入时,都会发送到接收缓冲区;此时操作系统会为新的客户端创建一个socket并进行一对一服务;
如果一瞬间有大量的套接字,大量的客户端同时给服务端发起请求,瞬间造成大量的资源请求;这样可能会导致资源耗尽,是非常危险的;因此要必须做出控制,限制同一时间所能创建的套接字的个数;

总结:如何连接请求:客户端发送请求给服务端,socket结构体为新的客户端在操作系统中重新建立了一个socket,端口依然是22号端口;此外还记录了客户端的地址信息;所以可以说为客户端重新创建了一个socket;
2.TCP简单编码程序:
(1)封装TCP的类:touch tcpsocket.hpp;
//使用C++封装TcpSocket类:
//创建套接字:
//bool socket();
//绑定地址地址信息:
//bool Bind(std::string &ip,uint16_t port);
//服务端开始监听:
//bool Listen(int backlog=5);
//客户端发起连接请求:
//bool connect(std::string &ip,uint16_t port);
//服务端获取已经连接完成的客户端新建socket:
//bool Accept (TcpSocket &clisock,struct sockaddr_in *addr);
//接收数据:
//bool Recv(std::string &buf);
//发送数据:
//bool Send(std::string &buf);
//关闭套接字:
//bool Close();
#include <string>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <string .h>
#define CHECK_RET(q) if((q)==false) { return -1;}
class TcpSocket{
pubilc:
TcpSocket():_sockfd(-1){
}
void SetSockFd(int fd){
_sockfd=fd;
}
bool Socket(){
_sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(_sockfd<0){
perror("socket error");
return false;
}
return true;
}
bool Bind(std::string &ip,uint16_t prot){
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr(ip.c_str());
socklen_t len=sizeof(struct sockaddr_in);
int ret=bind(_sockfd,(struct sockaddr*)&addr,len);
if(ret<0){
perror("bind error");
return false;
}
return true;
}
bool Listen(int backlog=10){
//int listen(int sockfd,int backlog);
//backlog:最大并发连接数-----内核中已完成连接队列的最大节点数;
int ret=listen(_sockfd,backlog);
if(ret<0){
perror("listen error");
return false;
}
return true;
}
bool Connect(std::string &ip,uint16_t port){
//int connect (int sockfd,const struct sockaddr *addr,socklen_t arrdlen);
//addr:要连接的服务端地址信息;
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr(ip.c_str());
socklen_t len=sizeof(struct sockaddr_in);
int ret=connect(_sockfd,(struct sockaddr*)&addr,len);
if(ret<0){
perror("connect error");
return false;
}
return true;
}
bool Accept(TcpSock &clisock,struct sockaddr_in *addr=NULL){
//int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
//addr:客户端的地址信息;
//len:输入输出参数,既要指定接收长度,又要接收实际长度;
//返回值:为新客户新建的socket套接字描述符;通过这个返回的描述符可以与指定的客户端进行通信;
struct sockaddr_in _addr;
socklen_t len=sizeof(struct sockaddr_in);
int newfd=accept(_sockfd,(struct sockaddr*)&addr,&len);
if(newfd<0){
perror("accept error");
return false;
}
if(addr!=NULL){
memcpy(addr,&_addr,len);
}
csock.SetSockFd(newfd);
//_sockfd ---仅用于接收新客户端连接请求;
//newfd---专门用于与客户端进行通信;
return true;
}
bool Recv(std::string &buf){
char tmp[4096]={0};
//ssize_t recv(int socket,void *buf,size_t len,int flags);
//flags: 0---默认阻塞接收; MSG_PEEK---获取数据,但是不从缓冲区移除;
//返回值:实际接收到的数据长度; 失败:-1; 连接断开:0;
int ret=recv(_sockfd,tmp,4096,0);
if(ret<0){
perror("recv error");
return false;
}
else if(ret==0){
printf("peer shutdown\n");
return false;
}
buf.assign(tmp,ret);
return true;
}
bool Send(std::string &buf){
//ssize_t send (int sockfd,const void *buf,size_t len, int flags);
int ret=send(_sockfd,buf.c_str(),buf.size(),0);
if(ret<0){
perror("send error");
return false;
}
return true;
}
private:
int _sockfd;
};
面试题:如何快速判断连接是否已经断开???
原理:TCP的连接管理中,内建有保活机制;当长时间没有数据往来的时候,每隔一段时间都会向对方发送一个保活探测包,要求对方回复;当多次发送的保活探测包都没有得到回复响应时,说明连接已经断开;
若连接断开,则recv返回0(不是没有数据的意思,而是连接断开);因为TCP面向字节流,有可能收到半条数据;因此一定要对返回值做出判断,判断数据是否已经完全接收或完全发送;
(2)创建TCP服务端程序:touch tcp_srv.cpp;
/*创建套接字;
绑定地址信息;
开始监听;
获取已经链接成功的客户端地址信息;
接收数据;
发送数据;
关闭套接字;*/
#include <iostream>
#include "tcpsocket.hpp"
int mian(int argc,char *argv[]){
if(argc!=3){
printf("./tcp_srv ip port\n");
return -1;
}
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
TcpSocket sock;
CHECK_RET(sock.Socket());
CHECT_RET(sock.Bind(ip,port));
CHECK_RET(sock.Listen());
while(1){
TcpSocket clisock;
struct sockaddr_in cliaddr;
//accept是阻塞获取已经完成的连接;
if(sock.Accept(clisock,&cliaddr)==false){
continue;
}
printf("new connect client:%s:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
std::string buf;
clisock.Recv(buf);
printf("client say:%s\n",buf.c_str);
buf.clear();
std::cout<<"srever say:";
fflush(stdout);
std::cin>>buf;
clisock.Send(buf);
}
sock.Close();
return 0;
}
(3)创建TCP客户端程序:touch tcp_cli.cpp;
/*使用封装的TcpSocket类,实现tcp客户端程序;
创建套接字;
向服务端发起连接请求;
发送数据;
接收数据;
关闭套接字;*/
#include <iostream>
#include "tcpsocket.hpp"
int main(int argc,char *argv[]){
if(argc!=3){
std::cout<<"./tcp_cli ip port\n";
return -1;
}
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
TcpSocket sock;
CHECK_RET(sock.Socket());
CHECK_RET(sock.Connect(ip,port));
while(1){
std::string buf;
std::cin>>buf;
std::cout<<"client say:";
fflush(stdout);
std::cin>>buf;
sock.Send(buf);
buf.clear();
sock.Recv(buf);
std::cout<<"server say:"<<buf<<std::cout;
}
sock.Close();
return 0;
}
该程序只能实现一对一进行通信;基本的TCP服务端程序只能与一个客户端通信一次,无法实现与多个客户端进行通信;
本质原因:因为不知道客户端数据到底什么时候到来,因此写死的程序很容易造成阻塞;
(1)不知道客户端的请求什么时候来;
(2)不知道客户端的数据什么时候来;
如何才能实现一对多进行通信????
如果要解决这个问题目前一个执行流是不能够解决的,一个执行流只能让一个流程往下走;因此就要设计多个执行流进行解决即------多进程或多线程;
二.多进程版本的TCP服务端程序(实现多对一通信):
创建多进程版本的服务端:tcp_process.cpp;
#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include "tcpsocket.hpp"
void sigcb(int no){
while(waitpid(-1,NULL,WNOHANG)>0); //不需要等待子进程退出循环,父进程就可返回;
}
int mian(int argc,char *argv[]){
if(argc!=3){
printf("./tcp_srv ip port\n");
return -1;
}
signal(SIGCHLD,sigcb); //自定义一个子进程的处理方式;
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
TcpSocket sock;
CHECK_RET(sock.Socket());
CHECT_RET(sock.Bind(ip,port));
CHECK_RET(sock.Listen());
while(1){
TcpSocket clisock;
//accept是阻塞获取已经完成的连接;
if(sock.Accept(clisock,&cliaddr)==false){
continue;
}
printf("new connect client:%s:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
int pid=fork();
if(pid==0){
while(1){
std::string buf;
clisock.Recv(buf);
printf("client say:%s\n",buf.c_str);
buf.clear();
std::cout<<"srever say:";
fflush(stdout);
std::cin>>buf;
clisock.Send(buf);
}
}
clisock.Close(); //只让子进程聊天,父进程把他关掉;
//父进程专门接收客户端请求;
}
sock.Close();
return 0;
}
三.多线程版本的TCP服务端程序(实现多对一通信):
创建多线程版本服务端:tcp_thread.cpp;
#include <iostream>
#include <pthread.h>
#include "tcpsocket.hpp"
void *thr_start(void *arg){
TcpSocket *sock=(TcpSocket*)arg;
while(1){
std::string buf;
sock->Recv(buf);
printf("client say:%s\n",buf.c_str);
buf.clear();
std::cout<<"srever say:";
fflush(stdout);
std::cin>>buf;
sock->Send(buf);
}
sock->Close();
delete sock;
return NULL;
}
int mian(int argc,char *argv[]){
if(argc!=3){
printf("./tcp_srv ip port\n");
return -1;
}
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
TcpSocket sock;
CHECK_RET(sock.Socket());
CHECT_RET(sock.Bind(ip,port));
CHECK_RET(sock.Listen());
while(1){
TcpSocket *clisock=new TcpSocket(); //数据段共享;
struct sockaddr_in cliaddr;
//accept是阻塞获取已经完成的连接;
if(sock.Accept(*clisock,&cliaddr)==false){
continue;
}
printf("new connect client:%s:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
pthread_t tid;
pthread_creat(&tid,NULL,thr_start,(void*)&clisock); ///创建线程入口函数;
pthread_detach(tid);
//多线程中主线程不能关闭socket,因为线程之间共享线程描述符表,如果在主线程中关闭socket,其他线程中的这个描述符表也就关闭了;但多进程可以,因为多进程中数据是独有的;
}
sock.Close();
return 0;
}
1551

被折叠的 条评论
为什么被折叠?



