TCP/IP Network Programming Chapter 5: Implementing TCP-based Server/Client (2)

Perfect implementation of echo client

In the previous chapter, we raised the problem of implementing the echo client, and here we solve it.

First give the corresponding server and client code:

while((str_len=read(clnt_sock,message,BUF_SIZE))!=0)
     write(clnt_sock,message,str_len);
write(sock,message,strlen(message));
str_len=read(sock,message,BUF_SIZE-1);

The problem with the above code is actually obvious, let's consider the server side first. Reads the contents of the string from the client until it is done, then writes everything read back to the client. Obviously the problem lies in the client. The client first writes its string content to the server, and then the client waits for the server to return, but it only calls the read function once, which is equivalent to only reading the string content once. We said in the previous chapter that the data transmitted by TCP has no data boundaries. In human terms, "the number of read calls is not equal to the number of writes". That is to say, in the case of the echo client, the client may need to call the read function multiple times to obtain the echo content.

Now give an example modification for the client code:

while(1){
    str_len=write(sock,message,strlen(message));
    
    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;
}
     

For the problem that the read function was only called once before, it is now changed to multiple times. In fact, the logic is very simple. It is to record the length of the echoed string. If the length is not reached, then wait for the call to read, otherwise, the echo is completed.

If the problem is not in the echo client: define the application layer protocol

If you think about it carefully, the root cause of the above problems is not that the client and server do not have a unified specification standard, what is needed at this time is the definition of the application layer protocol. In the process of sending and receiving data, rules need to be set to indicate the boundaries of the data, or the size of the sending and receiving data should be informed in advance.

Write a program below to experience the definition process of the application layer protocol. In this program, the server side obtains multiple numbers and operator information from the client side. After receiving the numbers, the server performs addition, subtraction, and multiplication operations, and then sends the results back to the client. For example, if 3, 5, and 9 are transmitted to the server while requesting addition, the client will receive the result of 3+5+9; if multiplication is requested, the client will receive the result of 3x5x9. However, if a subtraction is requested while passing 4, 3, and 2 to the server, the client will receive the operation result of 4-3-2, that is, the first parameter becomes the minuend.

Calculator server/client example

First stipulate the protocol of the application layer

□After the client connects to the server, it transmits the number of numbers to be calculated in the form of a 1-byte integer.

□Each integer data passed from the client to the server occupies 4 bytes.

□ Pass the operator after passing the integer data. Operator information occupies 1 byte.

□Select one of characters +, -, * to pass.
□The server returns the operation result to the client in the form of a 4-byte integer.
□The client terminal terminates the connection with the server end after getting the operation result.
This degree of agreement is equivalent to realizing half of the program, which also shows the importance of application layer protocol design in network programming.
As long as the protocol is well designed, the implementation should not be a big problem. In addition, as mentioned before, calling the close function will pass EOF to the other party,
please remember this and use it. The following is the calculator client code I implemented.

//头文件和其他示例相同,这里省略
#define BUF_SIZE 1024;
#define RLT_SIZE 4;
#defind OPSZ 4;
void error_handling(char*message);

int main(int argc,char*argv[]){
    int sock;  
    char opmsg[BUF_SIZE];
    int result,open_cnt,i;
    struct sockaddr_t serv_addr;
    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_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_addr.sin_port=htons(atoi(argv[2]));

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

    fputs("Operand count:",stdout);
    scanf("%d",&opnd_cnt);
    opmsg[0]=(char)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*0PSZ+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)
//与其他示例的error_handling函数相同,故省略。

The server-side code is given below:

#include<"与其他示例的头声明相同,故省略">
#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, 8);
    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, &cint_adr_sz);
      read(clnt_sock,&opnd_cnt,1);
      recv_len=0;

      while((opnd_cnt*0PSZ+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);
      }

      close(serv_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)
//与其他示例的error_handling函数相同,故省略。

The above client and server code may be a bit difficult in the part of the pointer, but a little understanding will do.

IO buffering in TCP sockets

There are some doubts here, that is, the server transmits 40 bytes at one time, but the client can accept them in batches. So if the client accepts 10 bytes, where should the remaining 30 bytes wait?

In fact, the write function call does not immediately transmit data, and the read function does not immediately receive data after calling. More precisely, the moment the write function is called, the data will be moved into the output buffer; the moment the read function is called, the data will be read from the output buffer.

These IO buffer characteristics can be summarized as follows:

□ IO buffer exists separately in each TCP socket.
□ IO buffer is automatically generated when creating a socket.
□ Even if the socket is closed, the remaining data in the output buffer will continue to be passed.
□ Closing the socket will lose the data in the input buffer.

Guess you like

Origin blog.csdn.net/Reol99999/article/details/131697007