copyright:weishusheng data:2015.5.26 編程
摘要:socket又叫套接字或者插口,它也是進程間通訊的一種方式,實際上就是網絡上的通訊節點,應用程序只須要連接到socket就能夠和網絡上任何一個通訊端點鏈接、傳送數據。socket封裝了通訊的細節,咱們能夠沒必要關心通訊協議內容而專一於應用程序開發。根據數據傳送方式,socket分爲面向鏈接的數據流通訊和無鏈接的數據報通訊。服務器
1.建立socket網絡
使用socket()函數建立socket對象,函數定義以下:dom
#include <sys/types.h>socket
#include <sys/socket.h>函數
int socket(int domain, int type, int protocol);ui
socket()函數參數詳解:spa
domain指定使用的域,一般取值爲AF_INET和AF_INET6,AF_INET表示使用IPv4協議,AF_INET6表示使用IPv6協議。type指定數據傳輸方式,type取值SOCK_STREAM表示面向鏈接的數據流方式,type取值SOCK_DGRAM表示無鏈接的數據報方式,type取值SOCK_RAW表示原始模式。protocol通常取0。socket()成功返回建立的函數句柄值。3d
2.面向鏈接的socket通訊實現指針
圖1 面向鏈接的socket數據流通訊
全部的面向鏈接socket數據流通訊都遵循這個過程。
2.1服務器端工做流程:
(1)使用socket()建立socket
(2)使用bind()把建立的socket綁定到指定TCP端口
(3)調用listen()使socket處於監聽狀態
(4)客戶端發送請求後,調用accept()接受客戶端請求,創建鏈接
(5)與客戶端發送或接收數據
(6)通訊完畢,關閉socket
2.2客戶端工做流程:
(1)使用socket()建立socket
(2)調用connect()向服務器端socket發起鏈接
(3)創建鏈接後,進行數據讀寫
(4)通訊完畢,關閉socket
2.3通訊過程使用了不一樣的函數,下面分別進行介紹
2.3.1 bind()函數
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
sockfd是要綁定的socket句柄,my_addr指向一個sockaddr結構,裏面保存IP地址和端口號,addrlen是sockaddr結構的大小。bind()若是綁定TCP端口成功,返回0,失敗返回-1。
2.3.2 listen()函數
#include <sys/socket.h>
int listent(int s, int backlog);
s 是要監聽的socket句柄,backlog指定最多能夠監聽的連接數量,默認是20個。若是listen調用成功,返回0,失敗就返回-1.
2.3.3 accept()函數
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t addrlen);
accept函數用於面向鏈接的套接字類型。accept()將從鏈接請求隊列中得到鏈接信息,建立新的套接字,並返回該套接字的描述符。accept返回的是一個新套接字描述符,客戶端能夠經過這個描述符和服務器通訊,而最初經過socket建立的套接字描述符依然用於監聽客戶端請求。
參數s是監聽的套接字描述符,參數addr是sockaddr結構的指針,addrlen是結構的大小。若是accept()調用成功,返回建立的套接字句柄,失敗返回-1,並設置全局變量爲errno。
2.3.4 connect()函數
客戶端建立套接字後就能夠用connect鏈接服務器。
#include <sys/types.h>
#include <sys/socket.h>
int connect (int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
connect()用於和服務器創建鏈接。sockfd是套接字句柄,serv_addr指向sockaddr結構,指定了服務器IP和端口;參數addlen是serv_addr結構大小。
2.3.5 客戶端和服務器可使用相同的發送和接受數據的函數,read()和write()就再也不講了;下面講一下send()和recv()函數。
(1)發送函數send()
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int s, const void *buf, size_t len, int flags);
參數s是套接字描述符,buf是要發送的數據緩衝,len是數據緩衝長度,flags通常置0;send若是發送成功返回發送的字節數,失敗返回-1.
(2)接收函數recv()
#include <sys/types.h>
#include <sys/socket.h>
int recv(int s, void *buf, size_t len, int flags);
參數s指定要讀取的套接字句柄,buf是存放數據的緩衝首地址,len指定接收緩衝大小,flags通常置0;recv讀取到數據時返回讀取到的字節數,失敗返回-1.另外,若是對方關閉了套接字,recv返回0;
3.面向鏈接的套接字實例
下面將以一個echo實例來講明socket通訊,其中echo_serv.c是服務器端代碼,當收到客戶端發送的字符就在屏幕上打印出來,並把字符串發送給客戶端,若是客戶端發送quit就結束。
(1)服務器端程序echo_serv.c
#include <string.h>
#define ECHO_PORT 8080
#define MAX_CLIENT_NUM 10
int main()
{
int sock_fd;
struct sockaddr_in serv_addr;
int clientfd;
struct sockaddr_in clientAdd;
char buff[101];
socklen_t len;
int n;
/* creat socket */
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if( sock_fd == -1)
{
perror("creat socket error!");
return 0;
}
else
{
printf("success to creat socket %d\n", sock_fd);
}
/*設置server地址結構*/
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(ECHO_PORT);
serv_addr.sin_addr.s_addr = htons(INADDR_ANY); //表示監聽全部客戶端ip,如需指定監聽指定的客戶端ip,可在此處指定,也可指定一個範圍
bzero(&(serv_addr.sin_zero), 8);
/*把地址和套接字綁定*/
if(bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) != 0)
{
printf("bind address fail! %d\n",errno);
close(sock_fd);
return 0;
}
else
{
printf("success to bind address! \n");
}
/*設置套接字監聽*/
if(listen(sock_fd, MAX_CLIENT_NUM) != 0)
{
perror("listen socket error!");
close(sock_fd);
return 0;
}
else
{
printf("success to listen!\n");
}
/*建立新連接對應的套接字*/
len = sizeof(clientAdd);
clientfd = accept(sock_fd, (struct sockaddr*)&clientAdd, &len);
if(clientfd <= 0)
{
perror("accept() error!\n");
close(sock_fd);
return 0;
}
/*接收用戶發來的數據*/
while((n = recv(clientfd, buff, 100, 0)) > 0)
{
buff[n] = '\n';
printf("number of receive bytes = %d data = %s\n",n, buff);
fflush(stdout);
send(clientfd, buff, n, 0);
if(strncmp(buff,"quit",4) == 0)
break;
}
close(clientfd);
close(sock_fd);
return 0;
}
INADDR_ANY選項
網絡編程中經常使用到bind函數,須要綁定IP地址,這時能夠設置INADDR_ANY.
INADDR_ANY就是指定地址爲0.0.0.0的地址,這個地址事實上表示不肯定地址,或「全部地址」、「任意地址」。
也就是表示本機的全部IP,由於有些機子不止一塊網卡,多網卡的狀況下,這個就表示全部網卡ip地址的意思。 好比一臺電腦有3塊網卡,分別鏈接三個網絡,那麼這臺電腦就有3個ip地址了,若是某個應用程序須要監聽某 個端口,那他要監聽哪一個網卡地址的端口呢?若是綁定某個具體的ip地址,你只能監聽你所設置的ip地址所在的網 卡的端口,其它兩塊網卡沒法監聽端口,若是我須要三個網卡都監聽,那就須要綁定3個ip,也就等於須要管理3個 套接字進行數據交換,這樣豈不是很繁瑣? 因此你只需綁定INADDR_ANY,管理一個套接字就行,無論數據是從哪一個網卡過來的, 只要是綁定的端口號過來的數據,均可以接收到。 固然, 客戶端connect時,不能使用INADDR_ANY選項。必須指明要鏈接哪一個服務器IP。
(2)下面是echo客戶端程序echo_client.c
#define ECHO_PORT 8080
#define MAX_COMMAND 5
int main()
{
int sock_fd;
struct sockaddr_in serv_addr;
char *buff[MAX_COMMAND] = {"abc", "def", "test", "hello", "quit"};
char tmp_buf[100];
int n,i;
/*creat socke*/
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if( sock_fd == -1)
{
perror("creat socket error!");
return 0;
}
else
{
printf("success to creat socket %d\n", sock_fd);
}
/*設置server地址結構*/
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(ECHO_PORT);
serv_addr.sin_addr.s_addr = htons(INADDR_ANY);
bzero(&(serv_addr.sin_zero), 8);
/*連接到服務器*/
if(-1 == connect(sock_fd, (struct sockaddr*)&serv_addr,sizeof(serv_addr)))
{
printf("connect error!\n");
close(sock_fd);
return 0;
}
printf("success connect to server!\n");
/*發送並接收緩衝的數據*/
for(i = 0; i < MAX_COMMAND; i++)
{
send(sock_fd, buff[i], 100, 0);
n = recv(sock_fd, tmp_buf, 100, 0);
tmp_buf[n] = '\n';
printf("data send:%s receive %s\n",buff[i], tmp_buf);
if(0 == strncmp(tmp_buf, "quit", 4))
break;
}
close(sock_fd);
return 0;
}
編譯客戶端和服務器端程序
[weishusheng@centOS6 echo_sock]$ gcc echo_client.c -o echo_client
[weishusheng@centOS6 echo_sock]$ gcc echo_serv.c -o echo_serv
運行服務器,輸出:
[weishusheng@centOS6 echo_sock]$ ./echo_serv
success to creat socket 3
success to bind address!
success to listen!
number of receive bytes = 100 data = abc
number of receive bytes = 100 data = def
number of receive bytes = 100 data = test
number of receive bytes = 100 data = hello
number of receive bytes = 100 data = quit
在另外一終端運行客戶端,輸出:
[weishusheng@centOS6 echo_sock]$ ./echo_client
success to creat socket 3
success connect to server!
data send: receive abc
data send:def receive def
data send:test receive test
data send:hello receive hello
data send:quit receive quit
4. socket通訊十分重要,應用普遍,它還有一種通訊方式,即無鏈接的socket通訊
無鏈接的socket通訊比較簡單,它使用UDP協議,不保證數據可否到達,用在數據要求不高的地方,如在線視頻。無鏈接的socket通訊不須要創建鏈接,省去了維護鏈接的開銷,因此速率更快。
無鏈接的socket數據報通訊流程如圖2
圖2 無鏈接的socket數據報通訊
和麪向鏈接的數據流通訊不一樣,無鏈接的socket數據報通訊在服務器綁定socket到指定IP和端口後,沒有調用listen()函數進行監聽;也沒有調用accept()函數對每一個新的請求創建鏈接,由於沒有鏈接的概念,傳輸層沒法區分不一樣的鏈接,也就不須要對每一個新的請求創建鏈接,在客戶端建立socket後能夠直接向服務器發送數據或者讀取數據。
無鏈接的socket數據報通訊發送和接收數據的函數和麪向鏈接的套接字通訊有點不一樣,分別使用recvfrom()和sendto()函數進行數據的發送和接收。它們的定義以下:
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
int sendto(int s, const void *msg, size_t len, int flags, struct sockaddr *to, socklen_t *tolen);
recvfrom用來從指定的IP和端口接收數據,s是socket句柄,buf是存放接收數據的緩衝首地址,len是接收緩衝大小,from是發送數據方的IP和端口號,fromlen是sockaddr結構大小。若是接收到數據,recvfrom返回接收到的字節數,失敗返回-1;
sendto發送數據到指定的IP和端口,s指定socket句柄,msg是發送的數據的緩衝首地址,len是緩衝大小,to指定接收數據的IP和端口號,tolen是sockaddr結構大小。sendto()若是調用成功返回發送的字節數,失敗返回-1。
無鏈接的時間服務通訊實例
該例子服務器負責建立socket,綁定IP和端口,而後等待客戶端發出請求當收到客戶端的請求「time」後,生成當前時間發送給客戶端。客戶端建立socket後,直接向服務器發送請求時間命令,以後等待服務器返回,發送退出命令,關閉鏈接。
(1)服務器端,time_serv.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#define TIME_PORT 9090
#define DATA_SIZE 256
int main()
{
int sock_fd;
struct sockaddr_in local;
struct sockaddr_in from;
int n;
socklen_t fromlen;
char buff[DATA_SIZE];
time_t cur_time;
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if( sock_fd <= 0)
{
perror("creat socket error!");
return 0;
}
perror("Creat socket");
/*設置server地址結構*/
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(TIME_PORT);
local.sin_addr.s_addr = htons(INADDR_ANY);
//local.sin_addr.s_addr = inet_addr("192.168.1.21");
bzero(&(local.sin_zero), 8);
if(0 != bind(sock_fd, (struct sockaddr*)&local, sizeof(local)))
{
perror("bind address fail!\n");
close(sock_fd);
return 0;
}
printf("bind socket!");
fromlen = sizeof(from);
printf("waiting request from client...\n");
while(1)
{
n = recvfrom(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&from, &fromlen);
if(n <= 0)
{
perror("recv data!\n");
lose(sock_fd);
return 0;
}
buff[n]='\n';
printf("client request:%s\n", buff);
if(0 == strncmp(buff, "quit", 4))
break;
if(0 == strncmp(buff, "time", 4))
{
cur_time = time(NULL);
printf("w1\n");
// strcpy(buff, asctime(gmtime(&cur_time)));
strcpy(buff, "weishusheng");
printf("now time is:%s",buff);
sendto(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&from, fromlen);
printf("w2\n");
}
}
close(sock_fd);
return 0;
}
(2)客戶端程序time_client.c
#define TIME_PORT 9090
#define DATA_SIZE 256
int main()
{
int sock_fd;
struct sockaddr_in serv;
int n;
socklen_t servlen;
char buff[DATA_SIZE];
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if( sock_fd <= 0)
{
perror("creat socket error!");
return 0;
}
perror("Creat socket");
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(TIME_PORT);
serv.sin_addr.s_addr = htons(INADDR_ANY);
//serv.sin_addr.s_addr = inet_addr("192.168.1.21");
bzero(&(serv.sin_zero), 8);
servlen = sizeof(serv);
strcpy(buff, "time");
if(-1 == sendto(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&serv, servlen))
{
perror("send data!");
close(sock_fd);
return 0;
}
printf("send time request\n");
n = recvfrom(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&serv, &servlen);
printf("program goes to recvfrom()\n");
if(n <= 0)
{
perror("recv data!\n");
close(sock_fd);
return 0;
}
buff[n]='\n';
printf("time from server:%s\n", buff);
strcpy(buff,"quit");
if(-1 == sendto(sock_fd, buff, sizeof(buff),0 , (struct sockaddr*)&serv, servlen))
{
perror("send data!");
close(sock_fd);
return 0;
}
printf("send quit command\n");
close(sock_fd);
return 0;
}
編譯運行便可看到客戶端和服務器的通訊過程。