一、基于TCP的客户端与服务器端
1.服务器端
初始化socket,绑定特定端口或地址(bind),开始监听(listen),调用accept()d等待客户端请求。接收到客户端请求后,开始连接。如果连接成功,服务器端接受客户端数据请求并处理(read),然后把回应数据返回给客户端。服务器端接收到客户端连接请求,close。
2.客户端
初始化socket,向服务器端发送连接请求。连接成功,发送数据给服务器端。接收服务器端回应数据之后,向服务器端发送连接结束请求,随后close。
二、相关函数分析
1.socket()
create an endpoint for communication.
创建一个端点用于交流。返回socket描述符SYNOPSIS #include/* See NOTES */ #include int socket(int domain, int type, int protocol);
其中
domain --- 域类型 type --- socket类型 protocol --- 协议类型- domain类型
Name | 执行的通信 | Purpose | 地址格式 | 地址结构 |
---|---|---|---|---|
AF_UNIX, AF_LOCAL | 内核中 | Local communication | 路径名 | sockaddr_un |
AF_INET | 通过ipv4 | Ipv4 Internet protocols | 32位ipv4地址+16位端口号 | sockaddr_in |
AF_INET6 | 通过ipv6 | Ipv6 Internet protocols | 128位ipv地址+16位端口号 | sockaddr_in6 |
- type类型
Name | Description | Translation |
---|---|---|
SOCK_STREAM | Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. | 提供序列的,可靠的,双向的,基于连接的字符流。 |
SOCK_DGRAM | Supports datagrams (connectionless, unreliable messages of a fixed maximum length). | 支持数据报(无连接,固定最大长度的不可靠信息) |
- protocol协议(待补充) The protocol specifies a particular protocol to be used with the socket.
2.bind()函数
bind a name to a socket.
绑定一个socket的名字。返回成功标志SYNOPSIS #include/* See NOTES */ #include int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
其中
sockfd --- socket描述符 addr --- 指向要绑定给sockfd的协议地址 addrlen --- 地址的长度- addr addr参数的真实数据结构将取决于地址族(AF),通用的数据结构如下:
struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
世界上有那么多服务器和客户端,究竟哪一个才是你需要的?此时我们就需要独一无二的地址来确定。而地址有很多种格式,常用的地址为32位地址+16位端口(ipv4),128位地址+16位端口(ipv6)。所以服务器在启动时需要绑定一个地址(bind),而客户端就不需要,系统会自动分配一个ip与端口。故而服务器端需要绑定,而客户端不需要。
- ipv4
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order 2字节*/ struct in_addr sin_addr; /* internet address 4字节*/ unsigned char sin_zero[8];};/* Internet address. */struct in_addr { uint32_t s_addr; /* address in network byte order */};
- ipv6
struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */};struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */};
3.地址转换函数
- 网络字节序 网络字节序定义:收到的第一个字节被当作高位看待,这就要求发送端发送的第一个字节应当是高位。而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。可见多字节数值在发送前,在内存中数值应该以大端法存放。 小端法(Little-Endian)就是低位字节排放在内存的低地址端即该值的起始地址,高位字节排放在内存的高地址端。 大端法(Big-Endian)就是高位字节排放在内存的低地址端即该值的起始地址,低位字节排放在内存的高地址端。
- 网络字节序转化
SYNOPSIS #includeuint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);DESCRIPTION The htonl() function converts the unsigned integer hostlong from host byte order to network byte order. 将32位的数据从主机字节序转换为网络字节序 The htons() function converts the unsigned short integer hostshort from host byte order to network byte order. 将16位的数据从主机字节序转换为网络字节序 The ntohl() function converts the unsigned integer netlong from network byte order to host byte order. 将32位的数据从网络字节序转换为主机字节序 The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order. 将16位的数据从网络字节序转换为主机字节序
- pton和ntop函数
SYNOPSIS #includeint inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
inet_pton()以及inet_ntop()可以实现网络字节序以及主机字节序的相互转化。这里有另外两个函数:inet_aton()和inet_ntoa()。这两个函数已经过时。其过时的原因是其不支持ipv6格式。
4.listen函数
SYNOPSIS #include/* See NOTES */ #include int listen(int sockfd, int backlog);
listen()使之前已创建的socket执行监听。也就是说这个socket专门负责监听来自客户端的请求(连接到此socket设定的ip与端口)。
对于 backlog 参数:
The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.
backlog参数定义了等待连接队列可能增长到的最大长度。如果当请求队列满时,一个连接请求到达了,那么这个客户端可能会收到一个错误(ECONNREFUSED)...
5.accept函数
SYNOPSIS #include/* See NOTES */ #include int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函数用于接收客户端的请求
The accept() system call is used with connection-based socket types (SOCK_STREAM, SOCK_SEQPACKET). It extracts the first connection request on the queue of pending connections for the listening socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state. The original socket sockfd is unaffected by this call. The argument addr is a pointer to a sockaddr structure. This structure is filled in with the address of the peer socket, as known to the communications layer. The exact format of the address returned addr is determined by the socket's address family (see socket(2) and the respective protocol man pages). When addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL. If no pending connections are present on the queue, and the socket is not marked as nonblocking, accept() blocks the caller until a connection is present. If the socket is marked nonblocking and no pending connections are present on the queue, accept() fails with the error EAGAIN or EWOULDBLOCK.
accept()系统调用被用于处理基于连接的socket类型。它获得监听socket等待队列中的第一个连接请求,并且创建一个新的socket,返回这个新的socket的文件描述符。新创建的socket不处在监听状态。原本的监听socket不受此新建立的socket影响。
参数addr是一个指向sockaddr结构的指针。这个结构被指向对于通讯层来说众所周知的对等地址(客户端的地址)... 如果此时队列中没有连接请求,那么这个socket不会被标记为非阻塞的。accept函数阻塞直到出现连接请求。如果这个socket被标记为非阻塞并且此时队列中无请求,那么accept函数返回error。6.connect函数
SYNOPSIS #include/* See NOTES */ #include int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
通常来说,基于连接协议的socket可能只能成功connect一次。这些socket可能通过多次连接改变他们之间的关系。
三、代码分析
- 服务器端
/********************************************************************** > File Name: socket_server.c > Author: 0nism > Email: fd98shadow@sina.com > Created Time: Sat 22 Sep 2018 12:55:55 UTC***********************************************************************/#include#include #include #include #include #include #include #include #include int main(int argc, char *argv[]){ // ipv4对应的地址族 struct sockaddr_in serv_addr; int listen_fd; int new_fd = -1; char buf[1024]; // int socket(int domain, int type, int protocol); listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) { printf("create socket failure: %s\n", strerror(errno)); return -1; } printf("socket create fd[%d]\n", listen_fd); // 使用地址serv_addr前必须初始化该片内存区域 memset(&serv_addr, 0, sizeof(struct sockaddr_in)); serv_addr.sin_family = AF_INET; // 将8889转换成网络字节序16位 serv_addr.sin_port = htons(8889); // 将INADDR_ANY 0.0.0.0转换成网络字节序32位 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); if ( bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0 ) { printf("create socket failure: %s\n", strerror(errno)); return -2; } printf("socket bind fd[%d] ok.\n", listen_fd); // int listen(int sockfd, int backlog); // 监听fd,并且设置最大上限为13 listen(listen_fd, 13); printf("socket start listen on port[%d] with fd[%d] ok.\n", 8889, listen_fd); while(1) { printf("start accept...\n", listen_fd); // int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // 不需要监听对端socket的地址与长度 new_fd = accept(listen_fd, NULL, NULL); if (new_fd < 0) { printf("accept new socket failure: %s\n", strerror(errno)); return -2; } printf("accepy ok, return new fd: [%d]\n", new_fd); memset(buf, 0, sizeof(buf)); read(new_fd, buf, sizeof(buf)); printf("read '%s' from client.\n", buf); write(new_fd, "goodbye", strlen("goodbye")); sleep(1); close(new_fd); } close(listen_fd);}
- 客户端
/********************************************************************** > File Name: socket_client.c > Author: 0nism > Email: fd98shadow@sina.com > Created Time: Sun 23 Sep 2018 07:53:09 UTC***********************************************************************/#include#include #include #include #include #include #include #include #define CONNECT_PORT 8889 #define SERVER_IP "127.0.0.1"int main(int argc, char *argv[]){ int conn_fd = -1; struct sockaddr_in serv_addr; char buf[1024]; conn_fd = socket(AF_INET, SOCK_STREAM, 0); if (conn_fd < 0) { printf("create socket failure: %s\n", strerror(errno)); return -1; } printf("socket create fd[%d]\n", conn_fd); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(CONNECT_PORT); inet_aton(SERVER_IP, &serv_addr.sin_addr); printf("socket start connect to server[%s:%d] with fd[%d]\n", SERVER_IP, CONNECT_PORT, conn_fd); // int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); if ( connect(conn_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("connect to server failure: %s\n", strerror(errno)); return -2; } write(conn_fd, "hello", strlen("hello")); memset(buf, 0, sizeof(buf)); read(conn_fd, buf, sizeof(buf)); printf("read %s\n from server\n", buf); sleep(1); close(conn_fd);}
四、独立代码
- 服务器端
/************************************************************************* > File Name: ex_socket_server.c > Author: 0nism > Mail: 3099456402@qq.com > Created Time: Sun 23 Sep 2018 10:19:08 UTC************************************************************************/#include#include #include #include #include #include #include #include #define PORT 8889#define ADDRESS "127.0.0.1"#define MESSAGE "goodbye!"int main(int argc, char *argv[]){ int server_fd = -1; int new_fd = -1; struct sockaddr_in server_addr; char buf[1024]; server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { printf("create socket failure: %s\n", strerror(errno)); return -1; } memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); // int inet_pton(int af, const char *src, void *dst); inet_pton(AF_INET, ADDRESS, &server_addr.sin_addr); if ( bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { printf("create socket failure: %s\n", strerror(errno)); return -2; } printf("bind ok. server_fd: [%d]\n", server_fd); listen(server_fd, 10); printf("socket start listening on port[%d], with fd[%d].\n", PORT, server_fd); while(1) { printf("start accept\n"); new_fd = accept(server_fd, NULL, 0); if (new_fd < 0) { printf("accept new socket failure: %s\n", strerror(errno)); return -3; } printf("accept ok, return new fd: [%d]", new_fd); memset(buf, 0, sizeof(buf)); read(new_fd, buf, sizeof(buf)); printf("read \n%s\nfrom client\n", buf); write(new_fd, MESSAGE, strlen(MESSAGE)); sleep(1); close(new_fd); } close(server_fd);}
- 客户端
/************************************************************************* > File Name: ex_socket_client.c > Author: 0nism > Mail: 3099456402@qq.com > Created Time: Sun 23 Sep 2018 12:00:00 UTC************************************************************************/#include#include #include #include #include #include #include #define PORT 8889#define ADDRESS "127.0.0.1"#define MESSAGE "oh! my god!"int main(int argc, char * argv[]){ int connect_fd; struct sockaddr_in connect_addr; char buf[1024]; connect_fd = socket(AF_INET, SOCK_STREAM, 0); if (connect_fd < 0) { printf("create socket failure: %s\n", strerror(errno)); return -1; } memset(&connect_addr, 0, sizeof(connect_addr)); connect_addr.sin_family = AF_INET; connect_addr.sin_port = htons(PORT); inet_pton(AF_INET, ADDRESS, &connect_addr.sin_addr); printf("socket start connect to server [%s:%d] with fd[%d]\n", ADDRESS, PORT, connect_fd); if ( connect(connect_fd, (struct sockaddr *)&connect_addr, sizeof(connect_addr)) < 0) { printf("connect socket failure: %s\n", strerror(errno)); return -2; } printf("connect ok!\n"); write(connect_fd, MESSAGE, sizeof(MESSAGE)); memset(buf, 0, sizeof(buf)); read(connect_fd, buf, sizeof(buf)); printf("read \n%s\nfrom server\n", buf); close(connect_fd);}