天天看点

socket网络通信<二>

socket网络通信

本文注意基于socket来分析TCP连接建立过程。

先回顾一下TCP连接建立过程:

socket网络通信<二>

主机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可总结如下:

socket网络通信<二>

总结:客户端的connect在三次握手的第二次返回,而服务器端的accept在第三次握手的第三次返回。

TCP连接释放过程:

socket网络通信<二>

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可总结如下:

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通信例程,上面的服务器使用的是迭代模式的,即只有处理完一个客户端请求才会去处理下一个客户端的请求,这样的服务器处理能力是很弱的,现实中的服务器都需要有并发处理。

继续阅读