socket函數簡述
int socket(int protofamily, int type, int protocol);
函數功能 :socket函數對應於普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用於創建一個socket描述符(socket descriptor),它唯一標識一個socket。這個socket描述字跟文件描述字一樣,後續的操作都有用到它,把它作爲參數,通過它來進行一些讀寫操作。
返回值: sockfd sockfd是描述符,也就是你所創建的套接字,類似一個門牌號
函數參數 : protofamily:即協議域,又稱爲協議族(family)。常用的協議族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作爲地址。
type:指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
protocol:就是指定協議。常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議
注意:並不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當protocol爲0時,會自動選擇type類型對應的默認協議。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數功能:bind()函數把一個地址族中的特定地址賦給socket,也可以說是綁定ip端口和socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。
函數參數:sockfd:即socket描述字,它是通過socket()函數創建了,唯一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。addr:一個const struct sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址創建socket時的地址協議族的不同而不同。addrlen:對應的是地址的長度。
通用函數類型:
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
}
如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 */
};
Unix域對應的是:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
int_addr_t indet_addr(const char *cp)
功能:將字符串形式的IP地址轉化爲整數型的IP地址(網絡字節序)
範例:int_addr.saddr=inet_addr("192.168.1.1");
char *inet_ntoa(struct in_addr)
功能:將整數形式的IP地址轉化爲字符串形式的IP地址
int listen(int sockfd, int backlog)
listen函數的第一個參數即爲要監聽的socket描述字,第二個參數爲相應socket可以排隊的最大連接個數。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變爲被動類型的,等待客戶的連接請求。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
connect函數的第一個參數即爲客戶端的socket描述字,第二參數爲服務器的socket地址,第三個參數爲socket地址的長度。客戶端通過調用connect函數來建立與TCP服務器的連接。成功返回0,若連接失敗則返回-1。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數sockfd就是上面解釋中的監聽套接字,這個套接字用來監聽一個端口,當有一個客戶與服務器連接時,它使用這個一個端口號,而此時這個端口號正與這個套接字關聯。當然客戶不知道套接字這些細節,它只知道一個地址和一個端口號。
參數addr
這是一個結果參數,它用來接受一個返回值,這返回值指定客戶端的地址,當然這個地址是通過某個地址結構來描述的,用戶應該知道這一個什麼樣的地址結構。如果對客戶的地址不感興趣,那麼可以把這個值設置爲NULL。
參數addrlen
如同大家所認爲的,它也是結果的參數,用來接受上述addr的結構的大小的,它指明addr結構所佔有的字節個數。同樣的,它也可以被設置爲NULL。
如果accept成功返回,則服務器與客戶已經正確建立連接了,此時服務器通過accept返回的套接字來完成與客戶的通信。
注意: accept默認會阻塞進程,直到有一個客戶連接建立後返回,它返回的是一個新可用的套接字,這個套接字是連接套接字。此時我們需要區分兩種套接字,監聽套接字: 監聽套接字正如accept的參數sockfd,它是監聽套接字,在調用listen函數之後,是服務器開始調用socket()函數生成的,稱爲監聽socket描述字(監聽套接字),連接套接字:一個套接字會從主動連接的套接字變身爲一個監聽套接字;而accept函數返回的是已連接socket描述字(一個連接套接字),它代表着一個網絡已經存在的點點連接。一個服務器通常通常僅僅只創建一個監聽socket描述字,它在該服務器的生命週期內一直存在。內核爲每個由服務器進程接受的客戶連接創建了一個已連接socket描述字,當服務器完成了對某個客戶的服務,相應的已連接socket描述字就被關閉。連接套接字socketfd_new 並沒有佔用新的端口與客戶端通信,依然使用的是與監聽套接字socketfd一樣的端口號(如果想讓多臺客戶端同時接入並工作,可使用多進程)
int recv( SOCKET s,char FAR *buf,int len, int flag)
不論是客戶還是服務器應用程序都用recv函數從TCP連接的另一端接收數據。
該函數的第一個參數指定接收端套接字描述符; 第二個參數指明一個緩衝區,該緩衝區用來存放recv函數接收到的數據;第三個參數指明buf的長度;第四個參數一般置0。
int send( SOCKET s, const char FAR *buf, int len, int flags )
不論是客戶還是服務器應用程序都用send函數來向TCP連接的另一端發送數據。
客戶程序一般用send函數向服務器發送請求,而服務器則通常用send函數來向客戶程序發送應答。第一個參數指定發送端套接字描述符;第二個參數指明一個存放應用程序要發送數據的緩衝區;第三個參數指明實際要發送的數據的字節數;第四個參數一般置0。
int close(int sockfd)
返回:若成功則返回0.失敗則返回-1;
close一個TCP套接字的默認行爲是把該套接字標記成已關閉,然後立即返回到調用程序。該套接字描述符不能再由調用進程使用,也就是說它不能再作爲read和write的第一個參數。然而TCP將嘗試發送已排隊等待發送到對端的任何數據,發送完畢後發生的是正常的TCP連接終止序列。
Socket流程
服務端:創建套接字->配置端口信息->綁定套接字->開始監聽->相關操作<-
客戶端:建套接字->配置連接端口信息->綁定套接字->連接->相關操作-<
簡單例子(來源我博哥,在此感謝博哥)
client
#include<netinet/in.h> // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<stdio.h> // printf
#include<stdlib.h> // exit
#include<string.h> // bzero
#define SERVER_PORT 8000
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
struct sockaddr_in client_addr;//客戶端結構體類型(內含有端口號,IPv4地址,以及地址族的結構體)
struct sockaddr_in server_addr;
int client_socket_fd;//客戶端socket返回值,用於判斷
char file_serve_name[FILE_NAME_MAX_SIZE+1];
char file_client_local[FILE_NAME_MAX_SIZE+1];
char buffer[BUFFER_SIZE];
FILE *fp;
int Start_Client_Socket()// 聲明並初始化一個客戶端的socket地址結構
{
bzero(&client_addr, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htons(INADDR_ANY);
client_addr.sin_port = htons(0);
return 1;
}
int Creat_Socket()// 創建socket,若成功,返回socket描述符
{
client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(client_socket_fd < 0)
{
perror("創建socket失敗:");
exit(1);
}
return 1;
}
int Client_Server_Bind() // 綁定客戶端的socket和客戶端的socket地址結構
{
if(-1 == (bind(client_socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr))))
{
perror("Client Bind Failed:");
exit(1);
}
return 1;
}
int Start_Serve_Socket()// 聲明一個服務器端的socket地址結構,並用服務器那邊的IP地址及端口對其進行初始化,用於後面的連接
{
char IP_serve_local[FILE_NAME_MAX_SIZE+1];
bzero(IP_serve_local, FILE_NAME_MAX_SIZE+1);
printf("請輸入服務器的IP地址:\t");
scanf("%s",IP_serve_local);
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
if(inet_pton(AF_INET, IP_serve_local, &server_addr.sin_addr) == 0)
{
perror("服務器IP錯誤:");
exit(1);
}
server_addr.sin_port = htons(SERVER_PORT);
return 1;
}
int Client_Serve_Link()// 向服務器發起連接,連接成功後client_socket_fd代表了客戶端和服務器的一個socket連接
{
socklen_t server_addr_length = sizeof(server_addr);
if(connect(client_socket_fd, (struct sockaddr*)&server_addr, server_addr_length) < 0)
{
perror("無法連接到服務器:");
exit(0);
}
return 1;
}
int File_LocalName_Trans()
{
bzero(file_serve_name, FILE_NAME_MAX_SIZE+1);
bzero(file_client_local, FILE_NAME_MAX_SIZE+1);
printf("請輸入要獲取的文件在服務器的位置及名字:\t");
scanf("%s",file_serve_name);
printf("請輸入要保存的文件在客戶端的位置及名字:\t");
scanf("%s",file_client_local);
bzero(buffer, BUFFER_SIZE);
strncpy(buffer, file_serve_name, strlen(file_serve_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_serve_name));
return 1;
}
int Send_File_Server_Name()// 向服務器發送要獲取的位置及名稱
{
if(send(client_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
{
perror("發送文件名稱錯誤:");
exit(1);
}
return 1;
}
int Write_Server_To_Client()
{
// 打開文件,準備寫入
fp = fopen(file_client_local, "w");
if(NULL == fp)
{
printf("File:\t%s 無法寫入\n", file_serve_name);
exit(1);
}
// 從服務器接收數據到buffer中
// 每接收一段數據,便將其寫入文件中,循環直到文件接收完並寫完爲止
bzero(buffer, BUFFER_SIZE);
int length = 0;
while((length = recv(client_socket_fd, buffer, BUFFER_SIZE, 0)) > 0)
{
if(fwrite(buffer, sizeof(char), length, fp) < length)
{
printf("File:\t%s 寫入失敗\n", file_serve_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}
printf("文件:\t%s 接收成功\n", file_serve_name);
return 1;
}
int Close_File_Socket()// 接收成功後,關閉文件,關閉socket
{
close(fp);
close(client_socket_fd);
return 1;
}
int main()
{
Start_Client_Socket();
Creat_Socket();
Client_Server_Bind();
Start_Serve_Socket();
Client_Serve_Link();
File_LocalName_Trans();
Send_File_Server_Name();
Write_Server_To_Client();
Close_File_Socket();
return 0;
}
sever
#include<netinet/in.h> // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<stdio.h> // printf
#include<stdlib.h> // exit
#include<string.h> // bzero
#define SERVER_PORT 8000
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t client_addr_length = sizeof(client_addr); // 定義客戶端的socket地址結構
int server_socket_fd;
int new_server_socket_fd;
int opt = 1;
int length = 0;
char buffer[BUFFER_SIZE];
char file_name[FILE_NAME_MAX_SIZE+1]; //從客戶端傳過來,要獲取的文件名字及地址
FILE *fp;
int Start_Server_Socket() // 聲明並初始化一個服務器端的socket地址結構
{
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
return 1;
}
int Creat_Socket() // 創建socket,若成功,返回socket描述符
{
server_socket_fd = socket(PF_INET, SOCK_STREAM, 0);
if(server_socket_fd < 0)
{
perror("創建socket失敗:");
exit(1);
}
setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
return 1;
}
int Client_Server_Bind() // 綁定客戶端的socket和客戶端的socket地址結構
{
if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))))
{
perror("Server Bind Failed:");
exit(1);
}
return 1;
}
int Serve_Listen() //不斷監聽從客戶端傳來的信息,每來一個就開啓新進程
{
if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE)))
{
perror("監聽失敗:");
exit(1);
}
return 1;
}
int Serve_Accept_Link() // 接受連接請求,返回一個新的socket(描述符),這個新socket用於同連接的客戶端通信
{ // accept函數會把連接到的客戶端信息寫到client_addr中
new_server_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length);
if(new_server_socket_fd < 0)
{
perror("Server Accept Failed:");
exit(0);
}
return 1;
}
int Send_File_Name() //接收需要發送的文件位置及文件名
{
bzero(buffer, BUFFER_SIZE);
if(recv(new_server_socket_fd, buffer, BUFFER_SIZE, 0) < 0) //recv函數接收數據到緩衝區buffer中
{
perror("接受文件名失敗");
exit(0);
}
// 然後從buffer(緩衝區)拷貝到file_name中
bzero(file_name, FILE_NAME_MAX_SIZE+1);
strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
printf("要發送的文件名爲:%s\n", file_name);
return 1;
}
int Document_Send()
{
// 打開文件並讀取文件數據
fp = fopen(file_name, "r");
if(NULL == fp)
{
printf("File:%s Not Found\n", file_name);
}
else
{
bzero(buffer, BUFFER_SIZE);
// 每讀取一段數據,便將其發送給客戶端,循環直到文件讀完爲止
while((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
{
if(send(new_server_socket_fd, buffer, length, 0) < 0)
{
printf("Send File:%s Failed./n", file_name);
exit(0);
}
bzero(buffer, BUFFER_SIZE);
}
fclose(fp); // 關閉文件
printf("文件:%s 發送成功\n", file_name);
}
return 1;
}
int Link_Trans()
{
while(1)
{
Serve_Accept_Link();
Send_File_Name();
Document_Send();
close(new_server_socket_fd); // 關閉與客戶端的連接
}
return 1;
}
int main(void)
{
Start_Server_Socket(); //服務器server初始化
Creat_Socket(); //創建服務器socket
Client_Server_Bind(); //客戶端與服務器綁定
Serve_Listen(); //服務器進行監聽
Link_Trans(); //鏈接並且進行文件的傳輸
close(server_socket_fd); //關閉監聽用的socket
return 0;
}
函數的組合應用便可以實現網絡通信的功能
------------------------------------------------------- -------------正在更新中--------------------------------------------------------------------------------------------------------------------------------------------------最後修改時間:2019年4月10日16:11:50-------------------------------------------------------------------