TCP/IP网络编程 学习笔记_5 --基于TCP的服务端/客服端

TCP服务端/客服端默认函数调用顺序
服务端顺序如下:
1,socket() 创建套接字
2,bind() 分配套接字地址
3,listen() 等待连接请求状态
4,accept() 连接(阻断函数,直到有连接请求后才返回)
5,read()/write() 数据交换
6,close() 断开连接

客服端顺序如下:
1,socket() 创建套接字
2,connect() 请求连接(要在listen()调用之后)
3,read()/write() 数据交换
4,close() 断开连接
 

下面再来分别讲讲上面提到的前面章节没讲解过的新接口:

int listen(int sock, int backlog);
sock:服务端套接字文件描述符(监听套接字)
backlog:连接请求等待队列的大小。即客服端请求的连接会先放入这个队列中排队,等待处理。如果队列长度为5,则表示最多使5个连接请求进入队列。

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
sock:服务端套接字的文件描述符
addr:保存发起连接请求的客服端地址信息的变量
addrlen:保存客服端地址长度
注:这个函数就是受理连接请求等待队列中待处理的客服端连接请求。它是个阻断函数,只有队列中有请求才会返回,并在其内部自动产生用于数据I/O的套接字。

int connect(int sock, struct sockaddr *servaddr, socklen_t addrlen);
sock:客服端套接字文件描述符
servaddr:保存目标服务端地址信息的变量
addrlen:服务端地址变量长度
注释:accept()要获取客服端的地址信息,但客服端压根就没有bind()分配地址这一步,那么客服端套接字地址信息在哪呢?其实是在调用connect()时,由操作系统自动分配的,IP用的主机的IP,端口随机。

ssize_t write(int fd, const void *buf, size_t nbytes);
fd:数据传输对象的文件描述符
buf:保存要传输数据的缓冲地址值
nbytes:要传输数据的字节数

ssize_t read(int fd, void *buf, size_t nbytes);
fd:数据接收数据的文件描述符
buf:保存要接收数据的缓冲地址值
nbytes:要接收数据的字节数
注释:
TCP套接字中的I/O缓冲:write()发送数据与read()接收数据它们各自对应有一个输出缓冲与输入缓冲。发送数据是先发往这个输出缓冲,再在适当时候(不管是分别传送还是一次性传送)发往read()对应的输入缓冲,而接收数据也就是从这个输入缓冲中取。
 

TCP套接字内部工作原理
1,TCP套接字从创建到消失一般分为如下3个过程:

与对方套接字建立连接
与对方套接字进行数据交换
断开与对方套接字的连接
2,与对方套接字建立连接模拟过程如下:

首先,请求连接的主机A向主机B传递如下信息:[SYN] SEQ: 1000, ACK: -;该消息中SEQ为1000,ACK为空,而SEQ为1000的含义如下:“现传递的数据包序号为1000,如果接收无误,请通知我向您传递1001号数据包”这是首次请求连接时使用的消息,又称SYN(表示收发数据前传输的同步消息)。
然后,主机B向主机A传递如下消息:[SYN + ACK] SEQ: 2000, ACK:1001;此时SEQ为2000,ACK为1001,而SEQ为2000的含义如下:“现传递的数据包序号为2000,如果接收无误,请通知我向您传递2001号数据包”而ACK为1001的含义如下:“刚才传输的SEQ为1000的数据包接收无误,现在请传递SEQ为1001的数据包”对主机A首次传输的数据包的确认消息(ACK:1001)和为主机B传输数据做准备的同步消息(SEQ:2000)捆绑发送,因此,此种类型的消息又称SYN+ACK.
最后观察主机A向主机B传输的消息:[ACK] SEQ:1001, ACK:2001;此时数据包传递如下消息:“已正确收到传输的SEQ为2000的数据包,现在可以传输SEQ为2001的数据包”这样就传输了添加ACK 2001的ACK消息。至此,主机A和主机B才确认建立了连接。这个连接经过了3次消息传递,因此,该过程又称三次握手。
3,与对方主机的数据交换

通过第一步三次握手过程完成了数据交换前的准备工作,下面再来看看TCP具体怎么交换数据的:假设主机A分2次向主机B传递200字节的过程。首先,主机A通过1个数据包发送100个字节的数据,数据包的SEQ为1200(SEQ 1200 100 byte data)。然后主机B向主机A发送ACK 1301消息确认(ACK号 = SEQ号 + 传递的字节数 + 1)。同上,主机A向主机B发送剩余的100字节数据(SEQ 1301 100 byte data),最后主机B向主机A确认收到ACK 1402。这就完成了200字节数据的传输。TCP就是这样,发送-确认-发送,有序安全的协议。
TCP套接字在SEQ发送了一个数据包后,会启动一个计时器以等待ACK回答,如果一定时间没有收到回复,它就会重传。
4,断开与套接字的连接

TCP套接字的结束过程也非常优雅,如果对方还有数据需要传输时直接断掉连接会出问题,所以断开连接时需要双方协商。过程如下:
先由主机A向主机B传递断开消息,然后,主机B发出确认消息 + 向主机A传递可以断开连接的消息,最后,主机A同样发出确认消息。这个过程经历4个阶段,因此又称四次握手。消息模拟过程是这样:[FIN] SEQ 5000 ACK - –> [ACK] SEQ 7500 ACK 5001 –> [FIN] SEQ 7501 ACK 5001 –> [ACK] SEQ 5001 ACK 7502。
实现回声服务端/客服端
什么是回声服务端/客服端?顾名思义,服务端将客服端传输的字符串数据原封不动地传回客服端,就像回声一样。
 

服务端代码
//
//  main.cpp
//  hello_server
//
//  Created by app05 on 15-7-6.
//  Copyright (c) 2015年 app05. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}


int main(int argc, const char * argv[]) {
    int serv_sock, clnt_sock;
    char message[BUF_SIZE];
    int str_len, i;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    if(argc != 2)
    {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1)
        error_handling("socket() error");

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)
        error_handling("bind() error");

    if(listen(serv_sock, 5) == -1)
        error_handling("listen() error");

    clnt_adr_sz = sizeof(clnt_adr);

    for (i = 0; i < 5; i++) {
        clnt_sock = accept(serv_sock, (struct sockaddr *) &clnt_adr, &clnt_adr_sz);
        if(clnt_sock == -1)
            error_handling("accept() error");
        else
            printf("Connected client %d \n", i+1);

        while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
            write(clnt_sock, message, str_len);

        close(clnt_sock);
    }

    close(serv_sock);
    return 0;
}

客服端代码
//
//  main.cpp
//  hello_client
//
//  Created by app05 on 15-7-6.
//  Copyright (c) 2015年 app05. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, const char * argv[]) {
    int sock;
    char message[BUF_SIZE];
    int str_len, recv_len, recv_cnt;
    struct sockaddr_in serv_adr;

    if(argc != 3)
    {
        printf("Usage: %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1)
        error_handling("socket() error");

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)
        error_handling("connect() error");
    else
        puts("Connected ...............");

    while (1) {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        str_len = write(sock, message, strlen(message));

        /*这里需要循环读取,因为TCP没有数据边界,不循环读取可能出现一个字符串一次发送
        但分多次读取而导致输出字符串不完整*/
        recv_len = 0;
        while (recv_len < str_len) {
            recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1);
            if(recv_cnt == -1)
                error_handling("read() error");
            recv_len += recv_cnt;
        }
        message[recv_len] = 0;
        printf("Message from server: %s", message);
    }

    close(sock);
    return 0;
}
 

这里写图片描述

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值