1.项目需求
实现回声服务器的客户端服务器程序,客户端通过网络连接到服务器,并发送任意一串英文信息,服务器端接受信息后,将每个字符转换为大写并回送给客户端显示。
2.知识准备
(1)网络通信与Socket
本示例来自于动脑学院Martin老师的免费课程
例子:90年代的通信
寄信者:
写好要发送的地址
还要写好相应的姓名,因为接受地址有很多人,到底属于谁接收就需要姓名来区分
然后放到邮筒中就可以
邮局:
传输信件
接受者:
由于信件比较多,小区让每家每户自己准备一个信箱,按图中步骤,最终挂于小区的传达室,传达室有信件来的话保安直接塞到信箱当中,这样信就属于收信者了,随时可以去取,为了能够接受信,收信者需要时不时来看一下信箱是否有信
Socket通信的三个要素:
通信的目的地址---------------------------------------邮件发送的地址
IP地址,我们平时的通信都是通过IP地址进行通信的
比如我们在浏览器中输入www.baidu.com,其实访问的是服务器,浏览器会首先查询域名对应的IP地址,我们可以通过 nslookup 域名 来查询域名对应的IP地址
每台主机的IP地址都是唯一的
端口号---------------------------------------------收件人的姓名
因为每台服务器可能安装了很多应用,我们到底要把数据传给哪个应用,就需要通过端口区分
http协议默认端口:80 (给http服务器用) SMTP协议默认端口:25(给邮件服务器用)
使用的传输层协议
TCP、UDP
而Socket就像上述例子中的邮筒
(2)Socket通信模型
不管是客户端还是服务器端,都会有自己的操作系统,而操作系统已经帮我们封装好了数据链路层,网络层和传输层的具体通信的细节都实现了封装,电信等ISP已经帮我们做好了物理层,于是客户端和服务器端通信的过程就是建立Socket的过程。
3.项目代码(待完善与测试)
服务器端代码:echo_server.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h> //网络的库
#include <string.h>
#include <ctype.h> //类型的定义
#include <arpa/inet.h> //地址转换
//IP地址
#define IP "0.0.0.0"
//定义端口
#define SERVER_PORT 888
perror_exit(const char * des){
fprintf(stderr, "%s error, reason: %s\n", des, strerror(errno));
//perror(des);
exit(1);
}
int main(void)
{
int sock;//代表信箱
int i, ret;
struct sockaddr_in server_addr; //服务器的地址
//1.美女创建信箱
sock = socket(AF_INET,SOCK_STREAM,0);//作用:创建套接字 第一个参数:网络通信家族 第二个参数:
使用TCP协议 第三个参数:一般填0
if(sock == -1){
perror_exit("create socket");
}
//2.清空标签,写上地址和端口号
bzero(&server_addr,sizeof(server_addr)); //把整个结构体清零
server_addr.sin_family =AF_INET;//选择协议族IPV4
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址,hton1是把字节顺序进行调整,把机器上的字节顺序调整为网络上的字节顺序
server_addr.sin_port =htons(SERVER_PORT);//绑定端口号,htons也是把主机字节顺序转变成网络字节顺序
//实现标签贴到信箱
ret=bind(sock,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(ret == -1){
perror_exit("bind");
}
//128同一时刻允许向客户端发起连接的数量
//把信箱挂载到传达室,这样就可以接信了
ret=listen(sock,128);
if(ret == -1){
perror_exit("listen");
}
printf("等待客户端的连接\n");
//服务器一直等等待接受来信
int done = 1;
while(done){
struct sockaddr_in client;
int client_sock, len;
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: %s\t port : %d\n",
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] = '\0';
printf("recive[%d]: %s\n", len, buf);
//转换成大写
for(i=0; i<len; i++){
/*if(buf[i]>='a' && buf[i]<='z'){
buf[i] = buf[i] - 32;
}*/
buf[i] = toupper(buf[i]);
}
len = write(client_sock, buf, len);
printf("write finished. len: %d\n", len);
close(client_sock);
}
}
步骤总结:
(1)创建服务器的socket
(2)创建服务器地址包括IP地址和端口号以及使用的协议族
(3)将socket和服务器地址绑定到一起
(4)监听端口
(5)接收客户端通信
(6)处理客户端请求
客户端:
//客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_PORT 666
#define SERVER_IP "127.0.0.1"
int main(int argc, char *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: %s\n", message);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, '\0', sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
servaddr.sin_port = htons(SERVER_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
write(sockfd, message, strlen(message));
n = read(sockfd, buf, sizeof(buf)-1);
if(n>0){
buf[n]='\0';
printf("receive: %s\n", buf);
}else {
perror("error!!!");
}
printf("finished.\n");
close(sockfd);
return 0;
}