1、套接字概述
套接字的本意是插座,在網絡中用來描述計算機中不同程序與其他計算機程序的通信方式。
常用的套接字類型有3種:
1)流套接字(SOCK——STREAM):使用了面向連接的可靠的數據通信方式,即TCP套接字;
2)數據報套接字(Raw Sockets):使用了不面向連接的數據傳輸方式,即UDP套接字;
3)原始套接字(SOCK——RAW):沒有經過處理的IP數據包,可以根據自己程序的要求進行封裝。
2、常用函數
1、創建套接字函數:成功時返回文件描述符,失敗時返回-1
int socket(int domain,int type,int protocol);
//參數domain用於指定創建套接字所使用的協議族(可取AF_UNIX,AF_INET,AF_INTE6)
//參數type指定套接字的類型(可取SOCK_STREAM,SOCK_DGRAM,SOCK_RAW)
//參數protocol通常設置為0
2、在指定套接字上創建鏈接函數:成功時返回0,失敗時返回-1
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
//參數sockfd是一個由函數socket創建的套接字
//參數serv_addr是一個地址結構,指定服務器的IP地址和端口號
//參數addrlen為參數serv_addr的長度
3、將一個套接字和某個端口綁定在一起的函數:成功時返回0,失敗時返回-1
int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen);
//一般只有服務器端的程序調用,參數my_addr指定了sockfd將綁定到的本地
//地址,可以將參數my_addr的sin_addr設置為INADDR_ANY而不是某個確定
//IP地址就可以綁定到任何網絡接口。
4、把套接字轉化為被動監聽函數:成功時返回0,失敗時返回-1
int listen(int s,int backlog);
//參數s為套接字,參數backlog指定鏈接請求隊列的最大長度;
5、接收連接請求函數:成功時返回文件描述符,失敗時返回-1
int accept(int s,struct sockaddr *addr,socklen_t *addrlen);
//參數s是由函數socket創建,經函數bind綁定到本地某一端口上,然後通過
//函數listen轉化而來的監聽套接字
//參數addr用來保存發起連接請求的主機的地址和端口
//參數addrlen是addr所指向的結構體的大小
6、在TCP套接字上發送數據函數:有連接
包含3要素:套接字s,待發數據msg,數據長度len
ssize_t send(int s,const void *msg,size_t len,int flags);
//函數只能對處於連接狀態的套接字使用,參數s為已建立好連接的套接字描述
//符,即accept函數的返回值
//參數msg指向存放待發送數據的緩沖區
//參數len為待發送數據的長度,參數flags為控制選項,一般設置為0
7、在TCP套接字上接收數據函數:有連接
包含3要素:套接字s,接收緩沖區buf,長度len
ssize_t recv(int s,void *buf,size_t len,int flags);
//函數recv從參數s所指定的套接字描述符(必須是面向連接的套接字)上接收
//數據並保存到參數buf所指定的緩沖區
//參數len則為緩沖區長度,參數flags為控制選項,一般設置為0
8、在UCP套接字上發送數據函數:無連接
ssize_t sendto(int s,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_t tolen);
//函數功能與函數send類似,但函數sendto不需要套接字處於連接狀態,所以
//該函數通常用來發送UDP數據,同時因為是無連接的套接字,在使用sendto時
//需要指定數據的目的地址,參數msg指向待發送數據的緩沖區。
//參數len指定了待發送數據的長度
//參數flags是控制選項,含義與send函數中的一致
//參數to用於指定目的地址,目的地址的長度由tolen指定
9、在UDP套接字上接收數據函數:無連接
ssize_t recvfrom(int s ,void *buf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen);
//與函數recv功能類似,只是函數recv只能用於面向連接的套接字,而函數
//recvfrom沒有此限制,可以用於從無連接的套接字上接收數據
//參數buf指向接收緩沖區
//參數len指定了緩沖區的大小
//參數flags是控制選項,含義與recv中的一致
//如果參數from非空,且該套接字不是面向連接的,則函數recvfrom返回時,
//參數from中將保存數據的源地址
//參數fromlen在調用recvfrom前為參數from的長度,調用recvfrom後將
//保存from的實際大小
10、關閉套接字函數:
int close(int fd);
//參數fd為一個套接字描述符;
11、多路復用函數:
int select(int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
//參數n是需要監視的文件描述符數
//參數readfds指定需要監視的可讀文件描述符集合
//參數writefds指定需要監視的可寫文件描述符集合
//參數exceptfds指定需要監視的異常文件描述符的集合
//參數timeout指定了阻塞的時間
3、服務器端套接字(接收連接請求的套接字)創建過程
第一步:調用socket函數創建套接字。
第二步:調用bind函數分配IP地址和端口號。
第三部:調用listen函數轉為可接收請求狀態。
第四步:調用accept函數受理連接請求。
4、客戶端套接字(發送連接請求的套接字)創建過程
只有兩步:
1、調用socket函數創建套接字。
2、調用connect函數向服務器端發送連接請求。
5、Linux的文件操作
文件描述符:是系統自動分配給文件或套接字的整數。
每當生成文件或套接字,操作系統就會自動返回給我們一個整數。這個整數就是文件描述符,即創建的文件或套接字的別名,方便稱呼而已。文件描述符在Windows中又稱為句柄。
1、打開文件:
2、關閉文件或套接字:
3、將數據寫入文件:
4、讀取文件中的數據:
注:ssize_t = signed int, size_t = unsigned int,都是通過typedef聲明,為基本數據類型取的別名。既然已經有了基本數據類型,那麼為什麼還需要為它取別名呢?是因為目前普遍認為int是32位的,而過去16位操作系統時代,int是16位的。根據系統的不同,時代的變化,基本數據類型的表現形式也隨著變化的。如果為基本數據類型取了別名,以後要修改,也就只需要修改typedef聲明即可,這將大大減少代碼變動。
6、服務器端實例
服務器端受理連接請求的程序。編譯並運行該程序,創建等待連接請求的服務器端。
#include
#include
#include
#include
#include
#include
void error_handling(char *message);
int main(int argc, const char * argv[])
{
int serv_sock;
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
char message[] = "Hello World!";
if(argc != 2)
{
printf("Usage:%s \n", argv[0]);
exit(1);
}
//(1)調用socket函數創建套接字
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1]));
//(2)調用bind函數分配IP地址和端口號
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
//(3)調用listen函數轉為可接收請求狀態
if(listen(serv_sock, 5) == -1)
error_handling("listen() error");
clnt_addr_size = sizeof(clnt_addr);
//(4)調用accept函數受理連接請求.沒有連接請求時調用不會返回,直到有請求
clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size);
if(clnt_sock == -1)
error_handling("accept() error");
//(5)write函數用於傳輸數據。執行到此行說明已有了連接請求
//向文件描述符為clnt_sock的客戶端文件中傳輸message中的數據
write(clnt_sock, message, sizeof(message));
close(clnt_sock);//(6)
close(serv_sock);//(7)
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
7、客戶端實例
#include
#include
#include
#include
#include
#include
void error_handling(char *message);
int main(int argc, const char * argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if(argc != 3)
{
printf("Usage: %s \n", argv[0]);
exit(1);
}
//(1)創建套接字.但此時並不馬上分為客戶端或服務器端
sock = socket(PF_INET, SOCK_STREAM, 0);
if(sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));
//(2)調用connect函數向服務器端發送連接請求.此時確定為客戶端
if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("connect() error");
//(3)調用read函數向自身聲明的message數組中保存數據
str_len = read(sock, message, sizeof(message) - 1);
if(str_len == -1)
error_handling("read() error");
printf("Message from server: %s \n", message);
close(sock);//(4)
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}