Linux平台C语言Socket编程练习之TCP套接字

0x00 要求

实现一个基于TCP协议的服务器-客户端程序,要求完成以下功能。

服务端:

1
2
3
接收客户的连接请求,并发送欢迎信息,显示客户的IP地址和端口号;
循环接收接收客户传来的字符串,反转后传递给客户;

客户端:

1
2
3
4
5
从命令行读入服务器的IP地址;并连接到服务器;
循环从命令行读入一行字符串,并传递给服务器,由服务器对字符串反转,并将结果返回客户程序,如果用户输入的是quit,则关闭连接;
客户程序显示反转后的字符串;

0x01 代码

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 1234
#define MAXNUMCLINET 20
#define MAXDATASIZE 100
int main()
{
int socketfd,bindfd,listenfd,connectfd,opt=SO_REUSEADDR;
ssize_t recv_num,send_num;
struct sockaddr_in server,client;
socklen_t addrlen;
char recv_buf[MAXDATASIZE],send_buf[MAXDATASIZE];
char wel[]="[*] Welcome! You can input 'quit' to exit:)";
if((socketfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket() error.");
exit(1);
}
setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr=htonl(INADDR_ANY);
if((bindfd=bind(socketfd,(struct sockaddr *)&server,sizeof(server)))==-1)
{
perror("bind() error.");
exit(1);
}
if((listenfd=listen(socketfd,MAXNUMCLINET))==-1)
{
perror("listen() error.");
exit(1);
}
if((connectfd=accept(socketfd,(struct sockaddr *)&client,&addrlen))==-1)
{
perror("accept() error.");
exit(1);
}
if((send(connectfd,wel,sizeof(wel),0))==-1)
{
perror("send welcome messages fail.");
exit(1);
}
printf("You got a connection from client IP:%s, PORT:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
while(1)
{
if((recv_num=recv(connectfd,recv_buf,MAXDATASIZE,0))==-1)
{
perror("recv() error.");
exit(1);
}
printf("[*] Received string:%s\n",recv_buf);
size_t len=strlen(recv_buf);
for(int i=0; i<(len/2); ++i)
{
char tmp=recv_buf[i];
recv_buf[i]=recv_buf[len-1-i];
recv_buf[len-1-i]=tmp;
}
strcpy(send_buf,recv_buf);
printf("[>] Send reverse string:%s\n",send_buf);
if((send_num=send(connectfd,send_buf,MAXDATASIZE,0))==-1)
{
perror("send() error.");
exit(1);
}
if(!strcmp(send_buf,"tiuq"))
{
close(connectfd);
break;
}
}
close(connectfd);
close(listenfd);
return 0;
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define PORT 1234
#define MAXDATASIZE 100
int main(int argc,char* argv[])
{
int socketfd;
ssize_t send_num,recv_num;
char send_buf[MAXDATASIZE],recv_buf[MAXDATASIZE],wel[MAXDATASIZE];
struct hostent* remote_host;
struct sockaddr_in server;
if(argc!=2)
{
printf("Usage:%s <IP address>\n",argv[0]);
exit(1);
}
if((remote_host=gethostbyname(argv[1]))==NULL)
{
perror("gethostbyname() error.");
exit(1);
}
if((socketfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket() error.");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr=*((struct in_addr*)remote_host->h_addr);
if((connect(socketfd,(struct sockaddr*)&server,sizeof(server)))==-1)
{
perror("connect() error.");
exit(1);
}
if ((recv(socketfd,wel,MAXDATASIZE,0))==-1)
{
perror("[!] Receive welcome message fail.");
exit(1);
}
wel[sizeof(wel)-1]='\0';
printf("%s\n", wel);
while(1)
{
printf("[*] Input string:");
scanf("%s",&send_buf);
send_buf[strlen(send_buf)]='\0';
if((strlen(send_buf))>100)
{
printf("[!] Only supports sending fewer than 100 characters.");
exit(1);
}
if((send_num=send(socketfd,send_buf,MAXDATASIZE,0))==-1)
{
perror("send() error.");
exit(1);
}
if((recv_num=recv(socketfd,recv_buf,MAXDATASIZE,0))==-1)
{
perror("recv() error.");
exit(1);
}
printf("[>] Received reverse string:%s\n",recv_buf);
if(!strcmp(recv_buf,"tiuq"))
{
close(socketfd);
break;
}
}
close(socketfd);
return 0;
}

0x02 演示

服务端:

Linux C socket programming practice of the TCP socket server demonstration.png

客户端:

Linux C socket programming practice of the TCP socket client demonstration.png

0x03 心得

在编写代码时一定注意程序的逻辑,先做什么,再做什么,同时要注意服务端和客户端的处理同步。在调用函数时要注意传入的参数类型的一致性以及函数返回值的类型,还有调用bind、accept、connect函数是要注意把server,client的sockaddr_in套接字地址结构强制转化为通用套接字地址结构sockaddr,类似:

1
bindfd=bind(socketfd,(struct sockaddr *)&server,sizeof(server))

中的(struct sockaddr *)&server。还要注意一个坑,在服务端accept函数的第一个参数必须是socket函数产生的套接字描述符,在本例正确的写法是这样的:

1
2
3
4
5
...
if((socketfd=socket(AF_INET,SOCK_STREAM,0))==-1)
...
if((connectfd=accept(socketfd,(struct sockaddr *)&client,&addrlen))==-1)
...

如果你像我一开始写成:

1
if((connectfd=accept(listenfd,(struct sockaddr *)&client,&addrlen))==-1)

就会报类似的错误:

1
accept() error.: Socket operation on non-socke

原因是虽然socket()、bind()、listen()、accept()等函数返回的值都是int类型的,但是socket()返回值的意义和其他的函数返回值却不相同,socket()返回的是一个套接字描述符,而其他函数调用成功返回0,出错返回-1,并置errno值,所以要注意这个区别。
另外在判断字符是否相同不要用”==”等号去判断:

1
if(send_buf=="tiuq")

而是最好使用strcmp()函数:

1
if(!strcmp(send_buf,"tiuq"))

要不然而出现类似的警告:

1
warning: comparison with string literal results in unspecified behavior [-Waddress]

虽然不会出现error,但是你懂的,处女座( •̀ ω •́ )y
在拷贝字符串数组不要错误地使用”=”去赋值:

1
send_buf[MAXDATASIZE]=recv_buf[MAXDATASIZE];

正确的方法是使用strcpy()函数:

1
strcpy(send_buf,recv_buf);