以下說明Socket的用法。(主要是Server端)
使用到的函式庫:
#include <sys/socket>
(此函式庫Windows作業系統不會有喔)
Server端首先要創建socket,並綁定在某個port,等待聆聽client連線。
( 基本上順序是這樣:socket() -> bind() -> listen() -> accept() ,創建 socket 之後將socket bind port,開始listen 監聽port有沒訊息,accept 會讓程式block住直到收到訊息)
重要函式
1. int socket( int domain, int type, int protocol):創建socket
- domain:常用的有三種
1. AF_UNIX / AF_LOCAL:用在本機process間的傳輸
2. AF_INET:兩台主機透過網路進行資料傳輸 (IPv4)
3. AF_INET6: 兩台主機透過網路進行資料傳輸 (IPv6)
- type :socket的傳輸手段
1. SOCK_STREAM:TCP
2. SOCK_DGRAM:UDP
- protocol:設定socket的協定標準,一般都會設為0
- return值:成功返回 socket file descriptor,失敗回傳-1
1. AF_UNIX / AF_LOCAL:用在本機process間的傳輸
2. AF_INET:兩台主機透過網路進行資料傳輸 (IPv4)
3. AF_INET6: 兩台主機透過網路進行資料傳輸 (IPv6)
- type :socket的傳輸手段
1. SOCK_STREAM:TCP
2. SOCK_DGRAM:UDP
- protocol:設定socket的協定標準,一般都會設為0
- return值:成功返回 socket file descriptor,失敗回傳-1
2. int bind( int sockfd, struct sockaddr *addr, socklen_t addrlen):綁定socket到某地址與port
- sockfd:socket file descriptor
- addr:地址資訊
- addrlen:輸入addr大小
- return值:成功返回 0,失敗回傳-1
- addr:地址資訊
- addrlen:輸入addr大小
- return值:成功返回 0,失敗回傳-1
3. int listen( int sockfd, int backlog):通知OS此socket可以接受連線
- sockfd:socket file descriptor
- backlog:設定佇列(incoming queue)中所允許的連線數目
- return值:成功返回 0,失敗回傳-1
- backlog:設定佇列(incoming queue)中所允許的連線數目
- return值:成功返回 0,失敗回傳-1
4. int accept( int sockfd, struct sockaddr *addr, socklen_t *addrlen ):取得等待中的連線,會回傳新的sockfd服務這個連線用,原sockfd繼續在listen
- sockfd:socket file descriptor
- addr:儲存client位址資訊
- addrlen:設定為addr的資料結構大小
- return值:成功返回新連線的 socket descriptor,錯誤回傳-1
- addr:儲存client位址資訊
- addrlen:設定為addr的資料結構大小
- return值:成功返回新連線的 socket descriptor,錯誤回傳-1
如何設定地址給socket綁定用
1. 使用 getaddrinfo(): (較新的方法)
需要 # include <netdb.h>
int getaddrinfo( const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res)
- node:要連線的主機名稱或IP address
- service:port number(ex:"80")或者特定服務名稱(可在UNIX系統上的IANA Port List或/etc/services檔案中找到,像是http, ftp, telnet...)
- hints:已經填好位址相關資訊的 struct addrinfo
- res:結果會回傳到res,res是一個指向 addrinfo 鏈結串列的指標
- return值:成功時回傳0。若傳回值非零,可將傳回值傳遞給gai_strerror()可取得我們可讀的字串
struct addrinfo {
int ai_flags; // additional options, ex: AI_PASSIVE, AI_CANONNAME 等。
int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
int ai_protocol; // 用 0 當作 "any"
size_t ai_addrlen; // ai_addr 的大小,單位是 byte
struct sockaddr *ai_addr; // struct sockaddr_in 或 _in6
char *ai_canonname; // 典型的 hostname
struct addrinfo *ai_next; // 鏈結串列、下個節點
};
struct addrinfo hints, *res;
int sockfd;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // 無關協議
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // 自動填上本機的IP
getaddrinfo(NULL, "3490", &hints, &res); // PORT這裡設定3490
// 建立 socket //(這邊是簡化版,照理應該要查看 "res" 鏈結串列的每個成員,並進行錯誤檢查!)
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res); // 綁定好要記得釋放res空間
2. 使用 sockaddr_in:
struct sockaddr_in myaddr;
int s;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(3490);
// 可以指定一個 IP address:
inet_pton(AF_INET, "127.0.0.1", &(myaddr.sin_addr));
// 或者可以讓系統自動選一個 IP address:
myaddr.sin_addr.s_addr = INADDR_ANY;
s = socket(PF_INET, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)&myaddr, sizeof myaddr);
Sever端Socket程式範例
#define PORT "3490"
#define BACKLOG 10
int sockfd, new_fd;
struct addrinfo hints, *servinfo, *p;
struct sockaddr_storage their_addr;
char s[INET6_ADDRSTRLEN];
int rv;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // 連上localhost
if ( (rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0 ){
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// 以迴圈找出全部的結果,並綁定(bind)到第一個能用的結果
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,p->ai_protocol)) == -1) {
perror("server: socket");
continue;
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
close(sockfd);
perror("server: bind");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
return 2;
}
freeaddrinfo(servinfo);
if(listen(sockfd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
printf("server: waiting for connection...\n");
while(1){
socklen_t sin_size = sizeof(their_addr);
new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size);
if(new_fd==-1){
perror("accept");
continue;
}
printf("server accept.\n");
inet_ntop(their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
s, sizeof(s));
printf("server: got connection from %s\n", s);
// newfd is a new socket descriptor for the new connection.
// listenfd is still listening for new connections.
close(new_fd);
}
轉換 struct sockaddr 成 struct sockaddr_in 或 struct sockaddr_in6:
void *get_in_addr(struct sockaddr *sa){
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
各種 Address Struct
1. struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
}; (16 bytes)
2. struct sockaddr_in {
short int sin_family; // Address family, AF_INET
unsigned short int sin_port; // Port number
struct in_addr sin_addr; // Internet address (in_addr : 4 byte)
unsigned char sin_zero[8];
} (16 bytes)
3. struct in_addr {
uint32_t s_addr; // that's a 32-bit int
}; (4 bytes)
4. struct sockaddr_in6 {
u_int16_t sin6_family; // address family, AF_INET6
u_int16_t sin6_port; // port number, Network Byte Order
u_int32_t sin6_flowinfo; // IPv6 flow 資訊
struct in6_addr sin6_addr; // IPv6 address
u_int32_t sin6_scope_id; // Scope ID
}; (28 bytes)
5. struct in6_addr {
unsigned char s6_addr[16]; // IPv6 address
}; (16 bytes)
6. struct sockaddr_storage {
sa_family_t ss_family; // address family
// 這裡都是填充物(padding),依實作而定
char __ss_pad1[_SS_PAD1SIZE];
int64_t __ss_align;
char __ss_pad2[_SS_PAD2SIZE];
}; (128 bytes)
參考資料
1. http://beej-zhtw.netdpi.net/
沒有留言:
張貼留言