socket 類型
常見的socket有3種類型如下。
(1)流式socket(SOCK_STREAM )
流式套接字提供可靠的、面向連接的通信流;它使用TCP 協議,從而保證了數據傳輸的正確性和順序性。
(2)數據報socket(SOCK_DGRAM )
數據報套接字定義了一種無連接的服 ,數據通過相互獨立的報文進行傳輸,是無序的,並且不保證是可靠、無差錯的。它使用數據報協議UDP。
(3)原始socket(SOCK_RAW)
原始套接字允許對底層協議如IP或ICMP進行直接訪問,功能強大但使用較爲不便,主要用於一些協議的開發。
進行Socket編程,常用的函數有:
1.socket:創建一個socket
int socket(int family, int type, int protocol);
//family指定協議族;type參數指定socket的類型:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;protocol通常賦值"0", socket()調用返回一個整型socket描述符
2.bind:用於綁定IP地址和端口號到socket
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
//sockfd是一個socket描述符,my_addr是一個指向包含有本機IP地址及端口號等信息的sockaddr類型的針; addrlen常被設置爲sizeof(struct sockaddr),bind()函數在成功被調用時返回0;遇到錯誤時返回"-1"並將errno置爲相應的錯誤號
3.connect:該函數用於綁定之後的client端,與服務器建立連接
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
//sockfd是目的服務器的sockect描述符;serv_addr是服務器端的IP地址和端口號的地址,addrlen常被設置爲sizeof(struct sockaddr)。遇到錯誤時返回-1,並且errno中包含相應的錯誤碼
4.listen:設置能處理的最大連接數,listen()並未開始接受連線,只是設置sockect爲listen模式
int listen(int sockfd, int backlog);
// sockfd是socket系統調用返回的服務器端socket描述符;backlog指定在請求隊列中允許的最大請求數
5.accept:用來接受socket連接
int accept(int sockfd, struct sockaddr *addr, int *addrlen);
//sockfd是被監聽的服務器socket描述符,addr通常是一個指向sockaddr_in變量的指針,該變量用來存放提出連接請求的客戶端地址;addrten通常爲一個指向值爲sizeof(struct sockaddr_in)的整型指針變量。錯誤發生時返回一個-1並且設置相應的errno值
6.send:發送數據
int send(int sockfd, const void *msg, int len, int flags);
//sockfd是你想用來傳輸數據的socket描述符,msg是一個指向要發送數據的指針。 len是以字節爲單位的數據的長度。flags一般情況下置爲0
7.recv:接受數據
int recv(int sockfd,void *buf,int len,unsigned int flags);
//sockfd是接受數據的socket描述符;buf 是存放接收數據的緩衝區;len是緩衝的長度。flags也被置爲0。recv()返回實際上接收的字節數,或當出現錯誤時,返回-1並置相應的errno值。
8.sendto:發送數據,用於面向非連接的socket(SOCK_DGRAM/SOCK_RAW)
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to,int tolen);
//該函數比send()函數多了兩個參數,to表示目地機的IP地址和端口號信息,而tolen常常被賦值爲sizeof (struct sockaddr)。sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。
9.recvform: 接受數據,用於面向非連接的socket(SOCK_DGRAM/SOCK_RAW)
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
//from是一個struct sockaddr類型的變量,該變量保存源機的IP地址及端口號。fromlen常置爲sizeof(struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。Recvfrom()函數返回接收到的字節數或當出現錯誤時返回-1,並置相應的errno
幾個字節順序轉換函數:
htons() --"Host to Network Short" ; htonl()--"Host to Network Long"
ntohs() --"Network to Host Short" ; ntohl()--"Network to Host Long"
在這裏, h表示"host" ,n表示"network",s 表示"short",l表示 "long"。
地址轉換函數:
in_addr_t inet_addr(const char * strptr);
將字符串IP地址轉換爲IPv4地址結構in_addr值
char * inet_ntoa(struct in_addr * addrptr);
將IPv4地址結構in_addr值轉換爲字符串IP
域名和IP地址的轉換:
struct hostent *gethostbyname(const char *name);
函數返回一種名爲hostent的結構類型,它的定義如下:
struct hostent
{
char *h_name; /* 主機的官方域名 */
char **h_aliases; /* 一個以NULL結尾的主機別名數組 */
int h_addrtype; /* 返回的地址類型,在Internet環境下爲AF-INET */
int h_length; /*地址的字節長度 */
char **h_addr_list; /* 一個以0結尾的數組,包含該主機的所有地址*/
};
#define h_addr h_addr_list[0] /*在h-addr-list中的第一個地址*/
下面給出兩個示例,一個是面向TCP數據流的socket通信,一個是面向UDP數據報的socket通信
1.面向TCP數據流的socket通信的演示程序由基於TCP的服務器和基於TCP的客戶端程序組成。
TCP的服務器程序結構:
1.創建一個socket,用函數socket()
2.綁定IP地址、端口信息到socket上,用函數bind()
3.設置允許的最大連接數,用函數listen()
4.接受客戶端的連接,用函數accept()
5.收發數據,用send()、recv()或者read()、write()
6.關閉網絡連接
TCP的客戶端程序結構:
1.創建一個socket,用函數socket()
2.設置要連接的服務器的IP地址和端口屬性
3.連接服務器,用函數connet()
4.收發數據,用send()、recv()或者read()、write()
5.關閉網絡連接
TCP服務器和TCP客戶端的通信圖如下:
下面貼出TCP服務器的代碼tcp_server.c:
tcp_server.c
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <errno.h>
-
#include <string.h>
-
#include <netdb.h>
-
#include <sys/types.h>
-
#include <netinet/in.h>
-
#include <sys/socket.h>
-
#include <arpa/inet.h> //inet_ntoa()函數的頭文件
-
-
#define portnumber 3333 //定義端口號:(0-1024爲保留端口號,最好不要用)
-
-
int main(int argc, char *argv[])
-
{
-
int sockfd,new_fd;
-
struct sockaddr_in server_addr; //描述服務器地址
-
struct sockaddr_in client_addr; //描述客戶端地址
-
int sin_size;
-
char hello[]="Hello!
Are You Fine?\n";
-
-
-
/* 服務器端開始建立sockfd描述符 */
-
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:IPV4;SOCK_STREAM:TCP
-
{
-
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
-
exit(1);
-
}
-
-
/* 服務器端填充 sockaddr結構 */
-
bzero(&server_addr,sizeof(struct
sockaddr_in)); // 初始化,置0
-
server_addr.sin_family=AF_INET; // Internet
-
server_addr.sin_addr.s_addr=htonl(INADDR_ANY); // (將本機器上的long數據轉化爲網絡上的long數據)和任何主機通信 //INADDR_ANY
表示可以接收任意IP地址的數據,即綁定到所有的IP
-
//server_addr.sin_addr.s_addr=inet_addr("192.168.1.1"); //用於綁定到一個固定IP,inet_addr用於把數字加格式的ip轉化爲整形ip
-
server_addr.sin_port=htons(portnumber); // (將本機器上的short數據轉化爲網絡上的short數據)端口號
-
-
/* 捆綁sockfd描述符到IP地址 */
-
if(bind(sockfd,(struct
sockaddr *)(&server_addr),sizeof(struct
sockaddr))==-1)
-
{
-
fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
-
exit(1);
-
}
-
-
/* 設置允許連接的最大客戶端數 */
-
if(listen(sockfd,5)==-1)
-
{
-
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
-
exit(1);
-
}
-
-
while(1)
-
{
-
/* 服務器阻塞,直到客戶程序建立連接 */
-
sin_size=sizeof(struct sockaddr_in);
-
if((new_fd=accept(sockfd,(struct
sockaddr *)(&client_addr),&sin_size))==-1)
-
{
-
fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
-
exit(1);
-
}
-
fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); // 將網絡地址轉換成.字符串,並打印到輸出終端
-
-
//向客戶端程序寫入hello數組裏的字符
-
if(write(new_fd,hello,strlen(hello))==-1)
-
{
-
fprintf(stderr,"Write Error:%s\n",strerror(errno));
-
exit(1);
-
}
-
/* 這個通訊已經結束 */
-
close(new_fd);
-
/* 循環下一個 */
-
}
-
-
/* 結束通訊 */
-
close(sockfd);
-
exit(0);
- }
tcp_client.c
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <errno.h>
-
#include <string.h>
-
#include <netdb.h>
-
#include <sys/types.h>
-
#include <netinet/in.h>
-
#include <sys/socket.h>
-
#include <arpa/inet.h> //inet_ntoa()函數的頭文件
-
-
-
#define portnumber 3333 //定義端口號:(0-1024爲保留端口號,最好不要用)
-
-
int main(int argc, char *argv[])
-
{
-
int sockfd;
-
char buffer[1024];
-
struct sockaddr_in server_addr; //描述服務器的地址
-
struct hostent *host;
-
int nbytes;
-
-
/* 使用hostname查詢host 名字 */
-
if(argc!=2)
-
{
-
fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
-
exit(1);
-
}
-
-
if((host=gethostbyname(argv[1]))==NULL)
-
{
-
fprintf(stderr,"Gethostname error\n");
-
exit(1);
-
}
-
-
/* 客戶程序開始建立 sockfd描述符 */
-
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:Internet;SOCK_STREAM:TCP
-
{
-
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
-
exit(1);
-
}
-
-
/* 客戶程序填充服務端的資料 */
-
bzero(&server_addr,sizeof(server_addr)); // 初始化,置0
-
server_addr.sin_family=AF_INET; // IPV4
-
server_addr.sin_port=htons(portnumber); // (將本機器上的short數據轉化爲網絡上的short數據)端口號
-
server_addr.sin_addr=*((struct
in_addr *)host->h_addr); // IP地址
-
-
/* 客戶程序發起連接請求 */
-
if(connect(sockfd,(struct
sockaddr *)(&server_addr),sizeof(struct
sockaddr))==-1)
-
{
-
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
-
exit(1);
-
}
-
-
/* 連接成功了 */
-
if((nbytes=read(sockfd,buffer,1024))==-1)
-
{
-
fprintf(stderr,"Read Error:%s\n",strerror(errno));
-
exit(1);
-
}
-
buffer[nbytes]='\0';
-
printf("I have received:%s\n",buffer);
-
-
/* 結束通訊 */
-
close(sockfd);
-
exit(0);
- }
編譯tcp_server.c和tcp_client.c
gcc tcp_server.c -o tcp_server
gcc tcp_client.c -o tcp_client
運行tcp服務器段程序和客戶端程序,顯示過程截圖如下:
2.面向UDP數據報的socket通信的演示程序由基於UCP的服務器和基於UDP的客戶端程序組成。
UDP的服務器程序結構:
1.創建一個socket,用函數socket()
2.綁定IP地址、端口信息到socket上,用函數bind()
3.循環接受數據,用函數recvform()
4.關閉網絡連接
UDP的客戶端程序結構:
1.創建一個socket,用函數socket()
2.設置要連接的服務器的IP地址和端口屬性
3.發送數據,用函數sento()
4.關閉網絡連接
UDP服務器和UDP客戶端的通信圖如下:
下面貼出UDP服務器的代碼udp_server.c:
udp_serer.c
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <errno.h>
-
#include <string.h>
-
#include <unistd.h>
-
#include <netdb.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <sys/types.h>
-
#include <arpa/inet.h>
-
-
#define SERVER_PORT 8888 //定義端口號:(0-1024爲保留端口號,最好不要用)
-
#define MAX_MSG_SIZE 1024
-
-
void udps_respon(int sockfd)
-
{
-
struct sockaddr_in addr;
-
int addrlen,n;
-
char msg[MAX_MSG_SIZE];
-
-
while(1)
-
{ /* 從網絡上讀,並寫到網絡上 */
-
bzero(msg,sizeof(msg)); // 初始化,清零
-
addrlen = sizeof(struct sockaddr);
-
n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,(struct
sockaddr*)&addr,&addrlen); // 從客戶端接收消息
-
msg[n]=0;
-
/* 顯示服務端已經收到了信息 */
-
fprintf(stdout,"Server have received %s",msg); // 顯示消息
-
}
-
}
-
-
int main(void)
-
{
-
int sockfd; //socket描述符
-
struct sockaddr_in addr; //定義服務器起地址
-
-
/* 服務器端開始建立socket描述符 */
-
sockfd=socket(AF_INET,SOCK_DGRAM,0);
-
if(sockfd<0)
-
{
-
fprintf(stderr,"Socket Error:%s\n",strerror(errno));
-
exit(1);
-
}
-
-
/* 服務器端填充 sockaddr結構 */
-
bzero(&addr,sizeof(struct
sockaddr_in)); // 初始化,置0
-
addr.sin_family=AF_INET; // Internet
-
addr.sin_addr.s_addr=htonl(INADDR_ANY); // (將本機器上的long數據轉化爲網絡上的long數據)和任何主機通信 //INADDR_ANY
表示可以接收任意IP地址的數據,即綁定到所有的IP
-
//addr.sin_addr.s_addr=inet_addr("192.168.1.1"); //用於綁定到一個固定IP,inet_addr用於把數字加格式的ip轉化爲整形ip
-
addr.sin_port=htons(SERVER_PORT); // (將本機器上的short數據轉化爲網絡上的short數據)端口號
-
-
/* 捆綁sockfd描述符 */
-
if(bind(sockfd,(struct
sockaddr *)&addr,sizeof(struct
sockaddr_in))<0)
-
{
-
fprintf(stderr,"Bind Error:%s\n",strerror(errno));
-
exit(1);
-
}
-
-
udps_respon(sockfd); // 進行讀寫操作
-
close(sockfd);
- }
udp_client.c
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <errno.h>
-
#include <string.h>
-
#include <unistd.h>
-
#include <netdb.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <sys/types.h>
-
#include <arpa/inet.h>
-
-
#define SERVER_PORT 8888 //定義端口號:(0-1024爲保留端口號,最好不要用)
-
#define MAX_BUF_SIZE 1024
-
-
void udpc_requ(int sockfd,const struct
sockaddr_in *addr,int len)
-
{
-
char buffer[MAX_BUF_SIZE];
-
int n;
-
while(1)
-
{ /* 從鍵盤讀入,寫到服務端 */
-
printf("Please input char:\n");
-
fgets(buffer,MAX_BUF_SIZE,stdin);
-
sendto(sockfd,buffer,strlen(buffer),0,(struct
sockaddr*)addr,len);
-
bzero(buffer,MAX_BUF_SIZE);
-
}
-
}
-
-
int main(int argc,char **argv)
-
{
-
int sockfd; //socket描述符
-
struct sockaddr_in addr; //定義服務器起地址
-
-
if(argc!=2)
-
{
-
fprintf(stderr,"Usage:%s server_ip\n",argv[0]);
-
exit(1);
-
}
-
-
/* 建立 sockfd描述符 */
-
sockfd=socket(AF_INET,SOCK_DGRAM,0);
-
if(sockfd<0)
-
{
-
fprintf(stderr,"Socket Error:%s\n",strerror(errno));
-
exit(1);
-
}
-
-
/* 填充服務端的資料 */
-
bzero(&addr,sizeof(struct
sockaddr_in)); // 初始化,置0
-
addr.sin_family=AF_INET; // Internet
-
addr.sin_port=htons(SERVER_PORT);// (將本機器上的short數據轉化爲網絡上的short數據)端口號
-
if(inet_aton(argv[1],&addr.sin_addr)<0) /*inet_aton函數用於把字符串型的IP地址轉化成網絡2進制數字*/
-
{
-
fprintf(stderr,"Ip error:%s\n",strerror(errno));
-
exit(1);
-
}
-
-
udpc_requ(sockfd,&addr,sizeof(struct
sockaddr_in)); // 進行讀寫操作
-
close(sockfd);
- }
編譯udp_server.c和udp_client.c
gcc udp_server.c -o udp_server
gcc udp_client.c -o udp_client
運行udp服務器段程序和客戶端程序,顯示過程截圖如下: