在TCP/IP协议中,一个IP地址标识网络通讯中唯一一台主机,而一个IP地址+一个TCP(或UDP)端口号就可以标识网络通讯中的一个进程,此时的IP地址+端口号即称为socket。 内存中的多字节数据相对于内存地址有大小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,网络数据流同样也有大小端之分:先发出的数据是低地址,后发出的数据是搞地质,TCP/IP协议规定网络数据流采用大端字节序,即低地址高字节。 1)计算机分为大端机和小端机,那么如何使同样的C代码在大端机和小端机上都能正常运行呢?库函数一节为我们提供了转换的接口:
include<arpa/inet.h> unit32_t htonl(unit32_t hostlong);//32位的长整数从主机字节序转换为网络字节序 unit16_t htons(unit16_t hostshort);//16位的短整数从主机字节序转换为网络字节序 unit32_t ntohl(unit32_t netlong);//32位的长整数从网络字节序转换为主机字节序 unit16_t ntohs(unit16_t netshort);//16位的短整数从网络字节序转换为主机字节序2)由于网络传输是二进制比特流传输,所以必须将我们常用的十进制的IP地址与网络字节序的二进制形式的IP源码互相转换才可以将数据传输到准确的地址,下面是地址转换函数介绍:
int inet_aton(const char* cp, struct in_addr *inp);//将字符串cp的十进制转换为网络字节序的二进制形式后存储到inp中 char* inet_ntoa(struct in_addr *in);//将网络字节序的二进制形式转换为十进制的字符串形式,返回字符串的首地址1)结构体 IPV4套接字地址结构体:
struct sockaddr_in{ unit8_t sin_len; sa_famliy_t sin_famliy;//协议家族 in_port_t sin_port;//端口号 struct in_addr sin_addr;//IP地址 char sin_zero[8]; };通用套接字地址结构体:
struct sockaddr{ unit8_t sa_len; sa_famliy sa_famlity; char sa_data[14]; };2)相关函数 socket:
bind: listen: accept与connect: send和recv: close:server端即服务器端:服务器由于不知道客户何时回请求建立连接,所以必须绑定端口之后进行监听; client端即客户端:只需向服务器端发送连接请求(connect); 客户端主动发起请求连接,服务器接受连接请求,完成“三次握手”;服务器与客户端都可以发起断开连接请求,完成”四次挥手“的过程。 基本框架图(此图为摘录):
代码如下: server.c: #include<stdio.h> #include<stdlib.h> #include<sys/socket.h> #include<netinet/in.h> #include<unistd.h> #include<string.h> #include<sys/types.h> #define _PORT_ 9999 #define _BACKLOG_ 10 int main() { int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0){ perror("socket"); exit(1); } int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in server_socket; struct sockaddr_in client_socket; bzero(&server_socket, sizeof(server_socket)); server_socket.sin_family = AF_INET; server_socket.sin_addr.s_addr = htonl(INADDR_ANY); server_socket.sin_port = htons(_PORT_); if(bind(sock, (struct sockaddr*)&server_socket, sizeof(struct sockaddr_in)) < 0) { perror("bind"); close(sock); exit(2); } if(listen(sock, _BACKLOG_) < 0) { perror("listen"); close(sock); exit(3); } printf("bind and listen success, wait accept..\n"); while(1) { socklen_t len = 0; int client_sock = accept(sock, (struct sockaddr*)&client_socket, &len); if(client_sock < 0) { perror("accept"); close(sock); exit(4); } char buf_ip[INET_ADDRSTRLEN]; memset(buf_ip, '\0', sizeof(buf_ip)); inet_ntop(AF_INET,&client_socket.sin_addr, buf_ip, sizeof(buf_ip)); printf("get connect, ip is : %s port is : %d\n",buf_ip, ntohs(client_socket.sin_port)); while(1) { char buf[1024]; memset(buf, '\0',sizeof(buf)); read(client_sock, buf, sizeof(buf)); printf("client#:%s\n",buf); printf("server#:"); memset(buf, '\0', sizeof(buf)); fgets(buf, sizeof(buf), stdin); buf[strlen(buf)-1] = '\0'; write(client_sock, buf, strlen(buf)+1); printf("please wait...\n"); } } close(sock); return 0; }client.c:
#include<stdio.h> #include<unistd.h> #include<sys/socket.h> #include<sys/types.h> #include<string.h> #include<netinet/in.h> #include<arpa/inet.h> #define SERVER_PORT 9999 #define SERVER_IP "192.168.43.121" int main(int argc, char *argv[]) { if(argc != 2) { printf("Usage: client IP \n"); return 1; } char *str = argv[1]; char buf[1024]; memset(buf, '\0', sizeof(buf)); struct sockaddr_in server_sock; int sock = socket(AF_INET, SOCK_STREAM, 0); bzero(&server_sock, sizeof(server_sock)); server_sock.sin_family = AF_INET; inet_pton(AF_INET, SERVER_IP, &server_sock.sin_addr); server_sock.sin_port = htons(SERVER_PORT); int ret = connect(sock, (struct sockaddr*)&server_sock, sizeof(server_sock)); if(ret < 0) { printf("perror"); return 1; } printf("connect success....\n"); while(1) { printf("client#:"); fgets(buf, sizeof(buf), stdin); buf[strlen(buf)-1] = '\0'; write(sock, buf, sizeof(buf)); if(strncasecmp(buf, "quit", 4) == 0) { perror("perror"); break; } printf("please wait...\n"); read(sock, buf, sizeof(buf)); printf("server$: %s\n",buf); } close(sock); return 0; }打开一个terminal运行服务器端server,再打开另外一个terminal运行客户端client,并加上通过ifconfig查出的IP地址即可实现服务器端与客户端的连接。