经过前几篇的介绍,本文我们将进行编程实战,实现一个简单地服务器和客户端应用程序。
编写服务器程序
编写服务器应用程序的流程如下:
①、调用 socket()函数打开套接字,得到套接字描述符;
②、调用 bind()函数将套接字与 IP 地址、端口号进行绑定;
③、调用 listen()函数让服务器进程进入监听状态;
④、调用 accept()函数获取客户端的连接请求并建立连接;
⑤、调用 read()/recv()、write()/send() 与客户端进行通信;
⑥、调用 close()关闭套接字。
下面,我们就根据上面列举的步骤来编写一个简单的服务器应用程序,代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define SERVER_PORT 8888 //端口号不能发生冲突,不常用的端口号通常大于5000
int main(void){
struct sockaddr_in server_addr = {0};
struct sockaddr_in client_addr = {0};
char ip_str[20] = {0};
int sockfd, connfd;
int addrlen = sizeof(client_addr);
char recvbuf[512];
int ret;
/* 打开套接字,得到套接字描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sockfd) {
perror("socket error");
exit(EXIT_FAILURE);
}
/* 将套接字与指定端口号进行绑定 */
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (0 > ret) {
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 使服务器进入监听状态 */
ret = listen(sockfd, 50);
if (0 > ret) {
perror("listen error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 阻塞等待客户端连接 */
connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
if (0 > connfd) {
perror("accept error");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("有客户端接入...\n");
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
printf("客户端主机的IP地址: %s\n", ip_str);
printf("客户端进程的端口号: %d\n", client_addr.sin_port);
/* 接收客户端发送过来的数据 */
for ( ; ; ) {
// 接收缓冲区清零
memset(recvbuf, 0x0, sizeof(recvbuf));
// 读数据
ret = recv(connfd, recvbuf, sizeof(recvbuf), 0);
if(0 >= ret) {
perror("recv error");
close(connfd);
break;
}
// 将读取到的数据以字符串形式打印出来
printf("from client: %s\n", recvbuf);
// 如果读取到"exit"则关闭套接字退出程序
if (0 == strncmp("exit", recvbuf, 4)) {
printf("server exit...\n");
close(connfd);
break;
}
}
/* 关闭套接字 */
close(sockfd);
exit(EXIT_SUCCESS);
}
以上我们实现了一个非常简单地服务器应用程序,根据上面列举的步骤完成了这个示例代码,最终的功能是,当客户端连接到服务器之后,客户端会向服务器(也就是本程序)发送数据,在我们服务器应用程序中会读取客户端发送的数据并将其打印出来,就是这么简单的一个功能。
SERVER_PORT 宏指定了本服务器绑定的端口号,这里我们将端口号设置为 8888,端口不能与其它服务器的端口号发生冲突,不常用的端口号通常大于 5000。
另外,bind绑定的IP地址是 INADDR_ANY,即表示所有发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都由这个服务端进程进行处理。
编写客户端程序
接下来我们再编写一个简单地客户端应用程序,客户端的功能是连接上小节所实现的服务器,连接成功之后向服务器发送数据,发送的数据由用户输入。示例代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define SERVER_PORT 8888 //服务器的端口号
#define SERVER_IP "192.168.1.150" //服务器的IP地址
int main(void){
struct sockaddr_in server_addr = {0};
char buf[512];
int sockfd;
int ret;
/* 打开套接字,得到套接字描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sockfd) {
perror("socket error");
exit(EXIT_FAILURE);
}
/* 调用connect连接远端服务器 */
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT); //端口号
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP地址
ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (0 > ret) {
perror("connect error");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("服务器连接成功...\n\n");
/* 向服务器发送数据 */
for ( ; ; ) {
// 清理缓冲区
memset(buf, 0x0, sizeof(buf));
// 接收用户输入的字符串数据
printf("Please enter a string: ");
fgets(buf, sizeof(buf), stdin);
// 将用户输入的数据发送给服务器
ret = send(sockfd, buf, strlen(buf), 0);
if(0 > ret){
perror("send error");
break;
}
//输入了"exit",退出循环
if(0 == strncmp(buf, "exit", 4))
break;
}
close(sockfd);
exit(EXIT_SUCCESS);
}
代码不再说明!需要注意的是 SERVER_IP 和 SERVER_PORT 指的是服务器的 IP 地址和端口号,服务器的 IP 地址根据实际情况进行设置,服务器应用程序服务器端代码中我们绑定的端口号为 8888,所以在客户端应用程序中我们也需要指定 SERVER_PORT 为 8888。
编译测试
这里笔者将服务器程序运行在开发板上,而将客户端应用程序运行在 Ubuntu 系统,当然你也可以将客户端和服务器程序都运行在开发板或 Ubuntu 系统,这都是没问题的。
首先编译服务器应用程序和客户端应用程序:
编译得到 client 和 server 可执行文件,server 可执行文件在开发板上运行,client 可执行文件在 PC 端 Ubuntu 系统下运行。将 server 可执行文件拷贝到开发板目录下,如下所示:
在开发板执行 server:
接着在 Ubuntu 系统执行客户端程序:
客户端运行之后将会去连接远端服务器,连接成功便会打印出信息“服务器连接成功...”,此时服务器也会监测到客户端连接,会打印相应的信息,如下所示:
接下来我们便可以在客户端处输入字符串,客户端程序会将我们输入的字符串信息发送给服务器,服务器接收到之后将其打印出来,如下所示:
Tips : 如果连接出现问题如“connect error: No route to host”,可以参考这篇问题记录开发板和虚拟机socket报错“connect error: No route to host”。
总结
到此,socket编程的内容就告一段落,内容讲得非常浅,目的其实并不是让大家学会网络编程,旨在以引导大家入门为主,让大家对 socket 网络编程有一个基本的了解和认识。望诸君常学常新、学思践悟。