5.1 改进客户端

  1. //echo_client.c
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<string.h>
  5. #include<unistd.h>
  6. #include<arpa/inet.h>
  7. #include<sys/socket.h>
  8. #define BUF_SIZE 1024
  9. void error_handling(char *message);
  10. int main(int argc, char *argv[])
  11. {
  12. int sock;
  13. char message[BUF_SIZE];
  14. int str_len, recv_len, recv_cnt;
  15. struct sockaddr_in serv_adr;
  16. if(argc!=3){
  17. printf("Usage : %s <IP> <port>\n", argv[0]);
  18. exit(1);
  19. }
  20. sock=socket(PF_INET, SOCK_STREAM, 0);
  21. if(sock==-1)
  22. error_handling("socket() error");
  23. memset(&serv_adr, 0, sizeof(serv_adr));
  24. serv_adr.sin_family=AF_INET;
  25. serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
  26. serv_adr.sin_port=htons(atoi(argv[2]));
  27. if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
  28. error_handling("connect() error!");
  29. else
  30. puts("Connected.........");
  31. while(1)
  32. {
  33. fputs("Input message(Q to quit):", stdout);
  34. fgets(message, BUF_SIZE, stdin);
  35. if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"))
  36. break;
  37. //变化开始---------------------------------------------------------------------------
  38. str_len = write(sock, message, strlen(message));
  39. recv_len = 0;
  40. while(recv_len<str_len)
  41. {
  42. recv_cnt = read(sock, &message[recv_len], BUF_SIZE-1);
  43. if(recv_cnt==-1)
  44. error_handling("read() error!");
  45. recv_len+=recv_cnt;
  46. }
  47. message[recv_len]=0;
  48. //变化结束--------------------------------------------------------------------------
  49. printf("Message from server:%s",message);
  50. }
  51. close(sock);
  52. return 0;
  53. }
  54. void error_handling(char *message)
  55. {
  56. fputs(message, stderr);
  57. fputc('\n', stderr);
  58. exit(1);
  59. }

5.2 客户端的口头协议部分

关于协议,也就是事先商量好的事情。比如先前的例子就是关于按q终止操作,就是一个基本的协议。

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

#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char opmsg[BUF_SIZE];
    int result, opnd_cnt, i;
    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.....");
    fputs("Operand count:",stdout);
    scanf("%d", &opnd_cnt);

    for(i=0; i<opnd_cnt; i++)
    {
        printf("Operand %d:", i+1);
        scanf("%d", (int*)&opmsg[i*OPSZ+1]);
    }
    fgetc(stdin);
    fputs("Operator:", stdout);
    scanf("%c", &opmsg[opnd_cnt*OPSZ+1]);
    write(sock, opmsg, opnd_cnt*OPSZ+2);
    read(sock, &result, RLT_SIZE);

    printf("Operation result: %d \n", result);
    close(sock);
    return 0;
}
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
//op_server.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 1024
#define OPSZ 4

void error_handling(char *message);
int calculate(int opnum, int opnds[], char oprator);


int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    char opinfo[BUF_SIZE];
    int result, opnd_cnt, i;
    int recv_cnt, recv_len;
    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++)
    {
        opnd_cnt=0;
        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        read(clnt_sock, &opnd_cnt, 1);

        recv_len=0;
        while((opnd_cnt*OPSZ+1)>recv_len)
        {
            recv_cnt=read(clnt_sock, &opinfo[recv_len], BUF_SIZE-1);
            recv_len+=recv_cnt;
        }
        result=calculate(opnd_cnt, (int*)opinfo, opinfo[recv_len-1]);
        write(clnt_sock, (char*)&result, sizeof(result));
        close(clnt_sock);
    }
    return 0;    
}

int calculate(int opnum, int opnds[], char op)
{
    int result=opnds[0], i;
    switch(op)
    {
        case '+':
            for(i=1; i<opnum; i++)
                result+=opnds[i];
                break;
        case '-':
            for(i=1; i<opnum; i++)
                result -= opnds[i];
                break;
        case '*':
            for(i=1; i<opnum; i++)
                result *= opnds[i];
                break;
    }
    return result;
}

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

5.3 TCP原理

TCP的套接字从创建到消失所经历的过程分为如下3步。

  • 与对方套接字建立连接(在实际通信过程中需要经过3次握手)
  • 与对方套接字进行数据交换
  • 断开与对方套接字的连接(此过程经历4次握手)

套接字是以全双工方式工作的。也就是说,它可以双向传递数据。因此收发数据前需要做一些准备。

5.3.1 TCP内部工作原理 1:与对方套接字的连接

SYN(Synchronization)的含义为收发数据前传输的同步消息

  • Client: [SYN] SEQ:1000, ACK: -(其中SEQ为1000,ACK为空。该含义为客户端向服务端请求,现传递内容数据包序号为1000,接受无误请通知我向你传递序号为1001数据包)
  • Server: [SYN+ACK] SEQ:2000, ACK:1001SEQ为2000,ACK为1001。该含义为服务端向客户端发送消息, 现传递内容数据包序号为2000, 接受无误请通知我向你传递序号为2001数据包,同时请向我传递序号为1001数据包)
  • Client: [ACK] SEQ:1001, ACK: 2001(该含义是以收到2000数据包,现在可以传输2001数据包)

5.3.2 TCP内部工作原理 2:与对方主机交换数据

通过上述步骤后,开始进行收发数据。

  • Client: SEQ 1200 100byte data
  • Server: ACK 1301
  • Client: SEQ 1301 100bytes data
  • Server: ACK 1402

不难发现,ACK = SEQ + 传递字节数 + 1。这样可以判断,数据是否可以正常传输连接,如果服务端没有返回ACK可以证明出现超时等,未接受到数据,需要重新传送。

5.3.3 TCP内部工作原理 3:断开与套接字的连接

  • Client: [FIN] SEQ 5000, ACK -
  • Server: [ACK] SEQ 7500, ACK 5001
  • Server: [FIN] SEQ 7501, ACK 5001
  • Client: [ACK] SEQ 5001, ACK 7502

其中,传递了两次5001,第二次[FIN]包这个只是因为接收ACK消息后未接收数据而重传的。

简述过程,首先(1)请求断开连接(2)请稍等(3)准备OK (4)谢谢合作