1、TCP连接服务器端需要做的事情描述:
建立监听套接字(socket函数),绑定监听接口(bind函数),进行监听(listen),调用connect函数等待在监听套接字上等待连接套接字的描述符:
1. 创建监听套接字,并绑定监听接口:
int sockfd = socket(AF_INET,SOCK_STREAM,0); // AF_INET说明为 IPV4协议,SOCK_STREAM说明为套接字流
sockaddr_in servaddr; //ipv4支持的地址格式为 sockaddr_in,网络传输支持的地址格式为 sockaddr
//初始化监听套接字要绑定的地址,其中输入地址绑定为 INADDR_ANY
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT); //监听端口号为 PORT
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd,(sockaddr*)&servaddr,sizeof(servaddr));
2.进行监听并返回连接的套接字
int connfd;
listen(sockfd,LISTENQ); //LISTENQ为允许的最大的监听排队套接字,自定义
for(;;)
{
connfd = accept(sockfd,NULL,NULL);
//获得连接的套接字描述符,可以通过该描述符进行通信
}
定义 Tcp_serv()函数,返回一个已建立好并保持监听的套接字
int Tcp_serv()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
sockaddr_in servaddr;
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999); //监听端口号为 9999
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd,(sockaddr*)&servaddr,sizeof(servaddr));
listen(sockfd,8);
return sockfd;
}
2、TCP连接客户端做的事情:
建立套接字,设定服务器端的地址,调用connect将套接字连接到服务器
int sockfd = socket(AF_INET,SOCK_STREAM,0);
int n;
sockaddr_in servaddr;
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT); //与服务器端绑定的端口号相同的端口号
inet_pton(AF_INET,ADDR,&servaddr.sin_addr); //ADDR为服务器的IP地址,需要自定义
//重复建立连接,最多尝试10次,直到连接成功建立
//*****需要特别注意,当connect失败想要重连时,需要使用新的socket套接字描述符
for(; n != 10; ++n)
{
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(connect(sockfd,(sockaddr *)&servaddr,sizeof(servaddr)) >=0 )
break;
//connect失败,准备重连
cout << n << "times connect failed" << endl;
close(sockfd);
}
if(n == 10)
cout << “connect error” << endl;
//连接成功建立,使用套接字描述符 sockfd 与服务器端进行通信
定义 Tcp_cli()函数,返回一个已建立好并保持监听的套接字
int Tcp_cli()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
int n;
sockaddr_in servaddr;
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999);
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr); //以环回地址为例
for(; n != 10; ++n)
{
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(connect(sockfd,(sockaddr *)&servaddr,sizeof(servaddr)) >=0 )
break;
//connect失败,准备重连
cout << n << "times connect failed" << endl;
close(sockfd);
}
if(n == 10)
cout << “connect error” << endl;
return sockfd;
}
3、基于网络套接字的读取函数,实现每次读取返回一行,包含换行符:
//从套接字描述符sockfd中读取一行,并放入string参数中
int Read(int sockfd,string & str)
{
char * head = NULL;
size_t n = 0;
//将套接字描述符当做文件描述符来使用,打开其文件流,使用 getline()函数
FILE * input = fdopen(sockfd,"r");
//getline()函数从文件流中读取一行信息放入 head 地址空间中,若 head == NULL,则自动分配足够空间,返回读取的数据大小,***注:包含换行符,出错返回 <= 0
num = getline(&head,&n,inout);
if(num < 0)
return -1;
str = string(head);
free(head); //释放空间
return num;
}
4、基于网络套接字的写函数:
//将 string 参数中的字符全写到套接字 sockfd 中
size_t Write(int sockfd,const string & str)
{
char buf[BUFSIZE]; //BUFSIZE 为最大传输的串的长度,可自定义。假设: #define BUFSIZE 1024
size_t len = str.size();
ssize_t n;
char * ptr = buff;
strcpy(buff,str.c_str());
while(len > 0)
{
if((n = write(sockfd,ptr,len)) <= 0)
{
if(n < 0 && errno == EINTR)
n = 0;
else
return -1;
}
len -= n;
ptr += n;
}
return n;
}
5、一个例子:
服务器端:
//serv.cpp
int main(int argc,char ** argv)
{
int sockfd = Tcp_serv();
pid_t pid;
int connfd;
for(;;)
{
connfd = accept(sockfd,NULL,NULL);
if((pid = fork()) < 0)
{
cout << "fork error" << endl;
return -1;
}
else if(pid == 0)
{
//服务器实现回射功能
close(sockfd);
string msg;
while(Read(connfd,msg) > 0)
Write(connfd,msg);
exit(0);
}
close(connfd);
}
}
客户端:
//client.cpp
int main()
{
int sockfd = Tcp_cli();
string info;
while(Read(STDIN_FILENO,info) > 0)
{
Write(sockfd,info);
Read(sockfd,info);
cout << "come from net is : " << info << flush; //包含换行符
}
}