5.1 改进客户端
//echo_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
void error_handling(char *message);
int main(int argc, 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));
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;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
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:1001 (SEQ为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)谢谢合作