1.回声服务器代码实现
1.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>
#define SERVER_PORT 666
int main(void){
int sock;
struct sockaddr_in server_addr;
sock = socket(AF_INET, SOCK_STREAM, 0);
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(sock, 128);
printf("等待客户端的连接\n");
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);
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("receive[%d]: %s\n", len, buf);
for(i=0; i<len; i++){
buf[i] = toupper(buf[i]);
}
len = write(client_sock, buf, len);
printf("finished. len: %d\n", len);
close(client_sock);
}
close(sock);
return 0;
}
2.client.c
#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;
}
注:客户端运行时要带上要传输的信息。如图:
2.函数解析
1.socket
int socket(int domain, int type, int protocol);
2.bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3.listen
int listen(int sockfd, int backlog);
4. accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5.connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3.原理讲解
1.Socket 通信流程图
首先网络上两台主机要通信,肯定要有标识分别标记两台主机,这样它们才能找到对方。我们知道IP地址可以唯一标识一台主机,但在网络编程中通信的其实是两个进程,所以为了标识不同主机的不同进程我们使用IP地址加端口号的标识方法。
这在服务器端的代码表现在bind函数上。 bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) 其中server_addr是一个结构体,其中的成员保存了IP地址与端口号。这个函数将服务器端套接字和addr绑定在一起,这样客户端就能通过该IP地址与端口号找到服务器。(sockaddr与server_addr的区别:首先,它们都是结构体,但前者是IPV4使用的,后者是IPV6使用的。由于主机越来越多,IPV4可用的地址越来越少,于是有了IPV6。IPV6解决了IPV4地址不够用的问题,理论上未来是会取代IPV4的,但如今IPV4还是在被使用。Linux底层使用server_addr储存IP地址与端口号,但如果要使用IPV4,也就是socket函数输入的参数是AF_INET ,则要进行强制类型转换。总之是历史遗留问题。)
看到这可能会感到奇怪,为什么服务器端需要绑定IP地址与端口号,但客户端却没有相应的代码实现。难道客户端不需要吗?其实不然,只不过客户端绑定的这一步骤不需要我们用代码实现,计算机自己会实现。
完成了计算机的标记,我们就能通过IP地址与端口号的组合找到对方了。对于服务器端来说完成标记后,就需要监听(这个监听可以理解为服务器端不时的查看是否有连接,这个时间间隔应该是很短的)服务器端套接字,服务器端套接字与之前设置的IP地址与端口号是绑定的。这在代码里表现在 listen(sock, 128) 。
之后服务器端执行到 accept(sock, (struct sockaddr *)&client, &client_addr_len) 函数时会阻塞在这里。服务器端的任务就暂时到这了,接下来只等客户端发来连接请求。
在客户端完成绑定后,通过 connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) 函数向服务器端发起连接请求。如果两端使用的是TCP协议,这其中还会有一个三次握手的过程。如下图:
客户端一开始发送一个请求如图1,服务器端收到后会回复一个报文如图2,客户端收到2后会会回复3表示已收到2。服务器端收到3后三次握手完成,连接建立。服务器端通过accept函数获得客户端套接字,接下来就可以进行通信了。
2.总结
客户端与服务器端各自创建套接字,服务器端绑定IP地址与端口号,监听客户端连接请求,通过accept函数接收客户端信息(储存在作为参数传入accept的结构体中,包括客户端IP地址与端口号)与套接字(返回值)。
客户端通过connect函数连接服务器(服务器IP地址与端口号储存在作为参数传入connect的结构体中),客户端IP地址与端口号的绑定由计算机自己实现。
|