socket入門分析
一、基於TCP的客戶端與服務器端
1.服務器端
初始化socket,綁定特定端口或地址(bind),開始監聽(listen),調用accept()d等待客戶端請求。接收到客戶端請求後,開始連接。如果連接成功,服務器端接受客戶端數據請求並處理(read),然後把回應數據返回給客戶端。服務器端接收到客戶端連接請求,close。
2.客戶端
初始化socket,向服務器端發送連接請求。連接成功,發送數據給服務器端。接收服務器端回應數據之後,向服務器端發送連接結束請求,隨後close。
二、相關函數分析
1.socket()
create an endpoint for communication.
創建一個端點用於交流。返回socket描述符
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
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 <sys/types.h> /* See NOTES */
#include <sys/socket.h>
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
#include <arpa/inet.h>
uint32_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
#include <arpa/inet.h>
int 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 <sys/types.h> /* See NOTES */
#include <sys/socket.h>
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 <sys/types.h> /* See NOTES */
#include <sys/socket.h>
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 <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
通常來說,基於連接協議的socket可能只能成功connect一次。這些socket可能通過多次連接改變他們之間的關系。
三、代碼分析
- 服務器端
/**********************************************************************
> File Name: socket_server.c
> Author: 0nism
> Email: [email protected]
> Created Time: Sat 22 Sep 2018 12:55:55 UTC
***********************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
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: [email protected]
> Created Time: Sun 23 Sep 2018 07:53:09 UTC
***********************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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: [email protected]
> Created Time: Sun 23 Sep 2018 10:19:08 UTC
************************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#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: [email protected]
> Created Time: Sun 23 Sep 2018 12:00:00 UTC
************************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#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);
}
socket入門分析