实现一个回声服务器的C/S(客户端client/服务器server)程序,功能为客户端连接到服务器后,发送一串字符串,服务器接受信息后,返回对应字符串的大写形式给客户端显示。
例如:
客户端发送“this is a webserver example!
",
服务器返回"THIS IS A WEBSERVER EXAMPLE!
"
#include <stdio.h> //printf #include <stdlib.h> //exit #include <unistd.h> //read, write, close #include <sys/types.h> //socket, bind, listen, accept #include <sys/socket.h> //socket, bind, listen, accept #include <string.h> //strerror #include <ctype.h> //inet_ntop #include <arpa/inet.h> //inet_ntop #include <errno.h> //strerror #define SERVER_PORT 666 //出错处理 void perror_exit(const char* des) { fprintf(stderr, "%s error, reason: %sn", des, strerror(errno)); exit(1); } int main(void){ int sock;//代表信箱 int ret;//作为bind和listen的返回值,用于处理出错信息 struct sockaddr_in server_addr; //1.创建套嵌字(信箱)。成功:返回socket的文件描述符,失败:返回-1,设置errno sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == -1) { perror_exit("create socket"); } //2.清空服务器地址空间(标签),写上地址和端口号 bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET;//选择协议族IPV4 //inet_pton(AF_INET, "1.1.1.1", &server_addr.sin_addr.s_addr);//测试出错处理函数perror_exit server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址 server_addr.sin_port = htons(SERVER_PORT);//绑定端口号 //3. 实现标签贴到收信得信箱上 ret = bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)); if(ret == -1) { perror_exit("bind"); } //4. 把信箱挂置到传达室,这样,就可以接收信件了(监听客户端) ret = listen(sock, 128); if(ret == -1) { perror_exit("listen"); } //万事俱备,只等来信 printf("等待客户端的连接n"); //5. 处理客户端请求 int done =1; while(done){ struct sockaddr_in client; int client_sock, len, i; char client_ip[64]; char buf[256]; socklen_t client_addr_len; client_addr_len = sizeof(client); client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len); //打印客服端IP地址和端口号 printf("client ip: %st port : %dn", inet_ntop(AF_INET, &client.sin_addr.s_addr,client_ip,sizeof(client_ip)), ntohs(client.sin_port)); /*读取客户端发送的数据*/ len = read(client_sock, buf, sizeof(buf)-1); buf[len] = ' '; printf("receive[%d]: %sn", len, buf); //转换成大写 for(i=0; i<len; i++){ buf[i] = toupper(buf[i]); } len = write(client_sock, buf, len); printf("finished. len: %dn", len); close(client_sock); } //6. 关闭连接 close(sock); return 0; }
#include <stdio.h> //printf #include <stdlib.h> //exit #include <string.h> //memset, strlen #include <unistd.h> //read, write, close #include <sys/socket.h> //socket, connect #include <arpa/inet.h> //inet_pton #define SERVER_PORT 666 #define SERVER_IP "127.0.0.1" int main(int argc, char *argv[]){//argc表示传入命令的个数,argv表示传入的具体信息 int sockfd; char *message; struct sockaddr_in servaddr; int n; char buf[64]; //异常处理 if(argc != 2){ fputs("Usage: ./echo_client message n", stderr); exit(1); } message = argv[1];//传入的信息 printf("message: %sn", message); //1. 创建套嵌字 sockfd = socket(AF_INET, SOCK_STREAM, 0); memset(&servaddr, ' ', sizeof(struct sockaddr_in));//分配空间 //定义地址IP和端口 servaddr.sin_family = AF_INET; inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr); servaddr.sin_port = htons(SERVER_PORT); //2. 连接服务器 connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //3. 读写和服务器的交互信息 write(sockfd, message, strlen(message)); n = read(sockfd, buf, sizeof(buf)-1); if(n>0){ buf[n]=' '; printf("receive: %sn", buf); }else { perror("error!!!"); } printf("finished.n"); //4. 关闭连接 close(sockfd); return 0; }
我的echo_server.c程序在/share/echo_server文件夹下,echo_client.c程序在/share/echo_client文件夹下。
必须先运行服务器程序,再运行客户端程序。顺序不能反!!
root@lxb-virtual-machine:/# cd /share/echo_server
root@lxb-virtual-machine:/share/echo_server# gcc echo_server.c -o echo_server
root@lxb-virtual-machine:/share/echo_server# ./echo_server
全过程截图:
root@lxb-virtual-machine:/# cd /share/echo_client
root@lxb-virtual-machine:/share/echo_client# gcc echo_client.c -o echo_client
root@lxb-virtual-machine:/share/echo_client# ./echo_client "this is a webserver example!"
全过程截图:
首先我们进行感性的分析,用来理解各个步骤的用意。之后我们需要对里面涉及到的函数进行具体的分析。
我们在高中英语经常遇到的一道作文题就是“你是李华,请给国外的笔友Andy写信”,而我们网络通信也可以类比于“李华与国外笔友通信”的模型。这里我们将“李华”比作客户端,“国外笔友Andy”作为服务器端。
为了使“李华同学”与“国外笔友”能够交流,首先需要统一语言,同时约定好寄信方式,邮局寄信还是电子邮件之类的,(这就是“socket套嵌字”)。之后Andy准备好一个信箱,之后找一张标签纸(server_addr),整理干净这张标签纸(bzero函数),往上面写上自己的地址和门牌号,一切准备好后,将贴好标签纸的信箱挂到外面(listen函数),这样大家都能给Andy寄信。最后Andy只需要时不时去看看信箱有没有信,有的话把信的内容读出来(read函数),之后再写封回信寄回去(write函数)。
李信作为写信人就比较简单了,首先还是使用统一的寄信方式,往信封上写上自己要寄的地址和门牌号,也就是Andy家的地址和门牌号,之后与Andy联系上(connect函数)。接下来就可以给Andy写信(write函数),读Andy的回信(read函数)。收到回信,不想再和Andy通信了,这时就把两个人的联系断开(close函数)。
所属头文件
#include <sys/socket.h>
函数定义
int socket(int domain, int type, int protocol);
参数含义
所属头文件
#include <sys/socket.h>
函数定义
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数含义
所属头文件
#include <sys/socket.h>
函数定义
int listen(int sockfd, int backlog);
参数含义
所属头文件
#include <sys/socket.h>
函数定义
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数含义
所属头文件
#include <sys/socket.h>
函数定义
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数含义
所属头文件
#include <errno.h> #include <string.h>
函数定义
char *strerror(int errnum);
参数含义
感谢bilibili的Martin老师的视频: C语言/C++服务器开发】小白实现第一个服务器入门项目 网络通信与Socket 编程详解&源码分享,本篇博客也是基于Martin老师这个视频所做的。
评论已关闭。