從一個簡單的使用TCP例子開始socket編程,其基本步驟如下:
server client
+++++++ ++++++++
創建socket 創建socket
+++++++ ++++++++
| |
| |
| |
+++++++ ++++++++
地址賦值( 地址賦值(
自己的地址) 服務器地址)
+++++++ ++++++++
| |
| |
| |
++++++++ |
用bind綁定 |
socket和地址 |
++++++++ |
| |
| |
| |
+++++++ |
listen |
+++++++ |
| ++++++++++
| <------------------------------ connect 服務器
| ++++++++++
+++++++ |
accept |
+++++++ |
| |
| +++++++++
| recv 和send
| 進行數據處理
| +++++++++
+++++++++ |
用accept得到 |
的socket進行 |
recv 和 send |
+++++++++ |
| |
| |
| |
+++++++++ +++++++++
close socket close socket
+++++++++ +++++++++
根據以上步驟,服務器端的代碼爲
- #include <stdio.h>
- #include <string.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <stdlib.h>
- #include <syslog.h>
- #include <errno.h>
- #define MAX_LISTEN_NUM 5
- #define SEND_BUF_SIZE 100
- #define RECV_BUF_SIZE 100
- #define LISTEN_PORT 1010
- int main()
- {
- int listen_sock = 0;
- int app_sock = 0;
- struct sockaddr_in hostaddr;
- struct sockaddr_in clientaddr;
- int socklen = sizeof(clientaddr);
- char sendbuf[SEND_BUF_SIZE] = {0};
- char recvbuf[RECV_BUF_SIZE] = {0};
- int sendlen = 0;
- int recvlen = 0;
- int retlen = 0;
- int leftlen = 0;
- char *ptr = NULL;
- memset((void *)&hostaddr, 0, sizeof(hostaddr));
- memset((void *)&clientaddr, 0, sizeof(clientaddr));
- hostaddr.sin_family = AF_INET;
- hostaddr.sin_port = htons(LISTEN_PORT);
- hostaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- listen_sock = socket(AF_INET, SOCK_STREAM, 0);
- if(listen_sock < 0)
- {
- syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__);
- exit(1);
- }
- if(bind(listen_sock, (struct sockaddr *)&hostaddr, sizeof(hostaddr)) < 0)
- {
- syslog(LOG_ERR, "%s:%d, bind socket failed", __FILE__, __LINE__);
- exit(1);
- }
- if(listen(listen_sock, MAX_LISTEN_NUM) < 0)
- {
- syslog(LOG_ERR, "%s:%d, listen failed", __FILE__, __LINE__);
- exit(1);
- }
- while(1)
- {
- app_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &socklen);
- if(app_sock < 0)
- {
- syslog(LOG_ERR, "%s:%d, accept failed", __FILE__, __LINE__);
- exit(1);
- }
- sprintf(sendbuf, "welcome %s:%d here!/n", inet_ntoa(clientaddr.sin_addr.s_addr), clientaddr.sin_port);
- //send data
- sendlen = strlen(sendbuf) +1;
- retlen = 0;
- leftlen = sendlen;
- ptr = sendbuf;
- //while(leftlen)
- {
- retlen = send(app_sock, ptr, sendlen, 0);
- if(retlen < 0)
- {
- if(errno == EINTR)
- retlen = 0;
- else
- exit(1);
- }
- leftlen -= retlen;
- ptr += retlen;
- }
- //receive data
- recvlen = 0;
- retlen = 0;
- ptr = recvbuf;
- leftlen = RECV_BUF_SIZE -1;
- //do
- {
- retlen = recv(app_sock, ptr, leftlen, 0) ;
- if(retlen < 0)
- {
- if(errno == EINTR)
- retlen = 0;
- else
- exit(1);
- }
- recvlen += retlen;
- leftlen -= retlen;
- ptr += retlen;
- }
- //while(recvlen && leftlen);
- printf("receive data is : %s", recvbuf);
- close(app_sock);
- }
- close(listen_sock);
- return 0;
- }
客戶端代碼爲:
- #include <stdio.h>
- #include <string.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <syslog.h>
- #include <errno.h>
- #include <stdlib.h>
- #define MAX_LISTEN_NUM 5
- #define SEND_BUF_SIZE 100
- #define RECV_BUF_SIZE 100
- #define SERVER_PORT 1010
- int main()
- {
- int sock_fd = 0;
- char recvbuf[RECV_BUF_SIZE] = {0};
- char sendbuf[SEND_BUF_SIZE] = {0};
- int recvlen = 0;
- int retlen = 0;
- int sendlen = 0;
- int leftlen = 0;
- char *ptr = NULL;
- struct sockaddr_in ser_addr;
- memset(&ser_addr, 0, sizeof(ser_addr));
- ser_addr.sin_family = AF_INET;
- inet_aton("127.0.0.1", (struct in_addr *)&ser_addr.sin_addr);
- ser_addr.sin_port = htons(SERVER_PORT);
- sock_fd = socket(AF_INET, SOCK_STREAM, 0);
- if(sock_fd < 0)
- {
- syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__);
- exit(1);
- }
- if(connect(sock_fd, (struct sockaddr *)&ser_addr, sizeof(ser_addr)) < 0)
- {
- syslog(LOG_ERR, "%s:%d, connect socket failed", __FILE__, __LINE__);
- exit(1);
- }
- //receive data
- recvlen = 0;
- retlen = 0;
- ptr = recvbuf;
- leftlen = RECV_BUF_SIZE -1;
- //do
- {
- retlen = recv(sock_fd, ptr, leftlen, 0) ;
- if(retlen < 0)
- {
- if(errno == EINTR)
- retlen = 0;
- else
- exit(1);
- }
- recvlen += retlen;
- leftlen -= retlen;
- ptr += retlen;
- }
- //while(recvlen && leftlen);
- printf("receive data is : %s", recvbuf);
- sprintf(sendbuf, "hello server/n");
- //send data
- sendlen = strlen(sendbuf) +1;
- retlen = 0;
- leftlen = sendlen;
- ptr = sendbuf;
- // while(leftlen)
- {
- retlen = send(sock_fd, ptr, sendlen, 0);
- if(retlen < 0)
- {
- if(errno == EINTR)
- retlen = 0;
- else
- exit(1);
- }
- leftlen -= retlen;
- ptr += retlen;
- }
- close(sock_fd);
- }
現在一個簡單的使用tcp的socket通信的例子已經完成了,這裏有幾個需要說明的問題
1)頭文件:
sys/socket.h 包含了socket相關的函數,如socket,send 和recv, 以及struct sockaddr等
netinet/in.h 包含了地址結構,如struct sockaddr_in
errno.h 包含了errno 和 EINTR
syslog.h 包含了syslog相關的信息,其打印結果在/var/log/messages裏面
2)socket地址
對於IPv4來說,其地址用的是struct sockaddr_in,具體結構如下
其中sin_len我們一般不關注,也不填(只有在使用routing socket的時候纔用到,被內核用來處理各種協議簇的地址結構)。 bind, connect, sendto, 和 sendmsg會把socket地址從程序傳遞給內核; 而accept, recvfrom, recvmsg, getpeername, 和 getsockname會把地址從內核傳遞給程序。因爲不同協議簇的地址結構是不一樣的,所以必須要有一個通用的指針來傳遞地址,對於ANSI C來說我們一般使用void *,但是socket產生早於ANSI C,所以也就沒有使用這個機制,而是使用一個通用的地址結構struct sockaddr來處理的
- struct in_addr {
- in_addr_t s_addr; /* 32-bit IPv4 address */
- /* network byte ordered */
- };
- struct sockaddr_in {
- uint8_t sin_len; /* length of structure (16) */
- sa_family_t sin_family; /* AF_INET */
- in_port_t sin_port; /* 16-bit TCP or UDP port number */
- /* network byte ordered */
- struct in_addr sin_addr; /* 32-bit IPv4 address */
- /* network byte ordered */
- char sin_zero[8]; /* unused */
- };
IPv6的socket地址爲struct sockaddr_in6
- struct sockaddr {
- uint8_t sa_len;
- sa_family_t sa_family; /* address family: AF_xxx value */
- char sa_data[14]; /* protocol-specific address */
- };
對於sockaddr-in6來說,我們不能用通用的地址struct sockaddr來存儲了,而是產用新的通用地址結構struct sockaddr_storage,這個結構足夠大可以存儲任何系統支持的地址。
- struct in6_addr {
- uint8_t s6_addr[16]; /* 128-bit IPv6 address */
- /* network byte ordered */
- };
- #define SIN6_LEN /* required for compile-time tests */
- struct sockaddr_in6 {
- uint8_t sin6_len; /* length of this struct (28) */
- sa_family_t sin6_family; /* AF_INET6 */
- in_port_t sin6_port; /* transport layer port# */
- /* network byte ordered */
- uint32_t sin6_flowinfo; /* flow information, undefined */
- struct in6_addr sin6_addr; /* IPv6 address */
- /* network byte ordered */
- uint32_t sin6_scope_id; /* set of interfaces for a scope */
- };
幾種常見的地址結構 3) 相關函數的的length 對於從程序傳地址給內核的函數(如connect),其長度是一個整型值,告訴內核要copy的地址長度。 對於從內核傳遞給程序的函數(如accpt),其長度是一個整型指針,是一個value-result參數。有兩個目的:一告訴內核地址結構的長度,讓內核在copy的時候不要超過這個長度;二返回內核真正copy的長度。 4)字節序 socket相關的函數都是使用網絡字節序 5)地址轉換函數 inet_aton, inet_ntoa, and inet_addr把IPv4字符串地址轉爲32位的網絡字節序地址 inet_ptonand inet_ntop可以轉換IPv4和IPv6的地址 6)listen中的backlog 要知道這個值的含義先用說一下,對於一個listen的socket,有兩個隊列:一個是incomplete connection隊列(僅僅收到SYN);一個是complete connection隊列(三次握手完成)。accept函數就是在complete connection隊列中取一個socket。backlog就是指隊列的個數,但不行的是各個地方都沒有明確定義這個值,沒有說明究竟代表了哪個隊列,或是兩個隊列之和。一般來說可以 同時處理的連接數是backlog的1.5倍,很多地方都用5. 7) getsockname 和 getpeername 這兩個函數可以與socket關聯的地址,getsockname 和 getpeername分別得到自己和對端的地址
- struct sockaddr_storage {
- uint8_t ss_len; /* length of this struct (implementation dependent) */
- sa_family_t ss_family; /* address family: AF_xxx value */
- /* implementation-dependent elements to provide:
- * a) alignment sufficient to fulfill the alignment requirements of
- * all socket address types that the system support
- * b) enough storage to hold any type of socket address that the
- * system supports.
- */
- };
- int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen)
- int getpeername(int sockfd , struct sockaddr * peeraddr , socklen_t * addrlen );