socket网络通信
本文注意基于socket来分析TCP连接建立过程。
先回顾一下TCP连接建立过程:
主机A运行的是TCP客户端程序,主机B运行的是TCP服务器程序,最初两端TCP进程处于Closed态,A主动打开连接,对应客户端connect函数发起连接,B被动接受连接,对应于服务器listen函数。
服务器TCP进程先创建传输控制块TCB,准备接受客户进程的连接请求;
1 客户端进程也首先创建TCB,然后通过connect函数向服务器发送连接请求,同步位SYN=1(不携带数据),初始序列号seq=x,客户端进入SYN-SENT态;
2 服务器一直通过listen监控socket,接收到连接请求后,如同意连接,向客户端发射确认,ACK=1,SYN=1,ack=x+1,seq=y;服务器端进入SYN-RCVD;
3 客户端进程收到确认后,这时connect函数返回,对服务器进程再确认,ACK=1,seq=x+1,ack=y+1,客户端进入连接态ESTABLISHED 可读写;服务器收到再次确认后也进入ESTABLISHED 可读写。
对应socket可总结如下:
总结:客户端的connect在三次握手的第二次返回,而服务器端的accept在第三次握手的第三次返回。
TCP连接释放过程:
TCP进程双方都处于ESTABLISHED态进行读写通信,如果要释放连接:
1 客户端进程向服务器发出释放连接报文段,FIN=1,seq=u,并停止发送数据,此时客户端进入FIN-WAIT-1;
2 服务器收到释放报文请求后,发出确认ACK=1,ack=u+1,seq=v,然后进入CLOSED-WAIT态,客户端收到确认消息后进入FIN-WAIT-2态,并等待服务器发出连接释放报文;
3 服务器发出连接释放请求,FIN=1,ACK=1,ack=u+1,seq=w,并进入LAST-ACK;
4 客户端收到服务器释放连接请求后,发出确认,ACK=1,seq=u+1,ack=w+1,然后进入TIME-WAIT状态,此时还需在等2MSL(最长报文段寿命)后再进入CLOSED。B收到确认消息后立刻进入CLOSED。
对应socket可总结如下:
某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;接收到这个FIN的源发送端TCP对它进行确认。这样每个方向上都有一个FIN和ACK。
最后,通过例程实践,例程内容:
下面编写一个简单的服务器、客户端(使用TCP)——服务器端一直监听本机的6666号端口,如果收到连接请求,将接收请求并接收客户端发来的消息;客户端与服务器端建立连接并发送一条消息
服务器端代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[4096];
int n;
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}//调用socket函数
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//注意网络字节序与本机字节序
servaddr.sin_port = htons(6666);
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}//将socket绑定IP和端口
if( listen(listenfd, 10) == -1){
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}//listen 该端口下的socket,输入队列额度10
printf("======waiting for client's request======\n");
while(1){
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}//accept来自客户端进程,注意此时产生一个已建立socket描述符confd
n = recv(connfd, buff, MAXLINE, 0);//读入到buff
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
close(connfd);//结束
}
close(listenfd);
}
客户端代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;
if( argc != 2){
printf("usage: ./client <ipaddress>\n");
exit(0);
}
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s\n",argv[1]);
exit(0);
}
if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}//发送请求
printf("send msg to server: \n");
fgets(sendline, 4096, stdin);
if( send(sockfd, sendline, strlen(sendline), 0) < 0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}//send函数,发送sendline里内容
close(sockfd);
exit(0);
}
这是一个最基本socket通信例程,上面的服务器使用的是迭代模式的,即只有处理完一个客户端请求才会去处理下一个客户端的请求,这样的服务器处理能力是很弱的,现实中的服务器都需要有并发处理。