实验平台
linux
实验内容
编写TCP服务器和客户端程序,程序运行时服务器等待客户端连接。一旦连接成功,服务器显示客户端的IP地址和端口号,并向客户端发送字符串
实验原理
TCP是面向连接的通信,其主要实现过程如下:
我们将服务器代码分为两部分。
1. init_tcp_server() tcp服务器的初始化
2. main() 实现读写数据
这样做的好处是main函数不必写的特别冗长,利于维护。从框架上来说,服务器的初始化也与读、写无关。
tcp服务器的初始化----init_tcp_server()
1. 创建socket
sockfd = socket(AF_INET, SOCK_STREAM, 0); //AF_INT:ipv4, SOCK_STREAM:tcp协议
2. 设置socket 当然这一步可以省略
int32_t opt = 1;
ret= setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
详细说明:
3. 绑定(bind函数)
将socket和地址(包括ip,port)绑定。需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序
struct sockaddr_in serveraddr; //地址结构体
bind函数
bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))
4. listen监听,将接收到的客户端放入队列
listen(sockfd,10) //第二个参数是队列长度
5. 调用accept函数,从队列获取请求,返回socket描述符,如果没有请求(没有客户端连接),将会阻塞,直到获取请求
int fd=accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
至此服务器初始化完成,返回成功连接的套接字fd。
服务器端代码如下:tcpserver.c
#include #include#include#include#include#include#include#include
#define PORT 1234
#define BACKLOG 10
#define BUFFER_SIZE 100
/**
* @brief 初始化tcp服务器
* @param[in] listenfd 监听套接字
* @return -1 - 失败, socket 文件句柄 - 成功*/int32_t init_tcp_server(int32_t listenfd)
{structsockaddr_in server;structsockaddr_in client;
int32_t connectfd= 0;
int32_t addrlen;
int32_t ret= 0;
addrlen= sizeof(client);/**< 创建一个tcp套接字*/listenfd= socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1)
{
perror("create socket failed!\n");
exit(1);
}/**< 设置一个tcp套接字*/int32_t opt= 1;
ret= setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));if (ret < 0)
{
perror("set socket failed!\n");
exit(1);
}/**< 设置服务器监听所有的IP地址*/bzero(&server, sizeof(structsockaddr_in));
server.sin_family=AF_INET;
server.sin_port= htons(PORT); /**< 主机字节序转化成网络字节序*/server.sin_addr.s_addr=htonl(INADDR_ANY);/**< 与服务器进行绑定*/
if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
perror("bind error");
exit(1);
}/**< 监听*/
if (listen(listenfd, BACKLOG) == -1)
{
perror("listen error");
exit(1);
}/**< 等待客户端连接,如果没有,一直阻塞*/
if ((connectfd = accept(listenfd, (struct sockaddr *)&client, &addrlen)) == -1)
{
perror("accept error");
close(listenfd);
close(connectfd);
exit(1);
}
printf("You got a connection from client's ip is %s, port is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));returnconnectfd;
}intmain()
{
int32_t listenfd= 0;
int32_t connectfd= 0;char buf[BUFFER_SIZE] = "Welcome to my server";
connectfd=init_tcp_server(listenfd);
send(connectfd, buf, BUFFER_SIZE,0); /**< 发送信息到客户端*/close(connectfd);
close(listenfd);
}
客户端
同样,将客户端代码分成两部分:
1. init_tcp_client() tcp客户端的初始化
2. main() 实现读写数据
客户端的初始化较为简单,如上图,只要实现socket和connect函数即可。但是我们希望可以手动输入客户端连接的IP地址,便于以后扩展,因此需要给客户端初始化传入一个参数。例如,输入:
./tcpclient 127.0.0.1
客户端代码如下:tcpclient.c
#include #include#include#include#include#include#include#include
#define PORT 1234
#define BUFFER_SIZE 100int32_t init_tcp_client(char *ipaddr)
{int sockfd = 0;structsockaddr_in server;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("create socket failed!\n");
exit(1);
}
bzero(&server, sizeof(structsockaddr_in));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
inet_pton(AF_INET, ipaddr,&server.sin_addr.s_addr); /**< 点分十进制转换成二进制的网络字节序*/
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
perror("connect error");
exit(1);
}returnsockfd;
}
int32_t main(int argc, char*argv[])
{
int32_t sockfd, num;charbuf[BUFFER_SIZE];if (argc != 2)
{
printf("Usage:%s \n",argv[0]);
exit(1);
}
sockfd= init_tcp_client(argv[1]);if ((num = recv(sockfd, buf, BUFFER_SIZE, 0)) == -1)
{
perror("recv error");
exit(1);
}
buf[num- 1] = '\0';
printf("Server Message: %s\n", buf);
close(sockfd);return 0;
}
Makefile文件如下:
all:server client
server:tcpserver.c
gcc tcpserver.c-o server
client:tcpclient.c
gcc tcpclient.c-o client
clean:
rm-rf server client
实验结果如下: