套接字編程
+++++++套接字編程+++++++
(位於6-4的中間)
【socket( ); 創建套接字,返回一個套接字描述符】
int socket(int family, int type, int protocol);
family:協議族
AF_INET: IPv4協議
AF_INET6: IPv6協議
AF_LOCAL: UNIX域協議
FA_ROUTE: 路由套接字(socket)
AF_KEY: 祕鑰套接字(socket)
type:套接字類型
SOCK_STREAM: 字節流套接字(socket)
SOCK_DGRAM: 數據報套接字(socket)
SOCK_RAW: 原始套接字(socket)
protoco:0
<<成功:非負套接字描述符 出錯:-1>>
【bind( ); 爲創建的套接字通信接口綁定IP和端口】
int bind(int sock_fd, struct socdaddr *my_addr, int addrlen);
sockfd:套接字描述符
my_addr:本地地址
addrlen:地址長度
<<成功:0 出錯:-1>>
【listen( ); 設置套接字爲監聽狀態】
/*被動套接字,即套接字只能接受別的主機的請求,而不能主動連接別的主機*/
int listen(int sock_fd, int backlog);
sock_fd:套接字描述符
backlog:在同一時刻能接受的連接請求個數(5-10)
<<成功:0 出錯:-1>>
【accept( ); 接受連接請求】
服務端用
/* 接受一個新的連接請求,並返回一個新的套接字,這個套接字代表一個新的連接 */
int accept(int sock_fd, struct sockaddr *addr, socklen_t *addrlen);
sock_fd:套接字描述符
addr:用來保存發起連接請求的那一端的信息(ip和端口)。
addrlen:保存結構體長度地址
<<成功:非負套接字描述符 出錯:-1>>
【connect( ); 向服務器發起連接請求】
/* 客戶端來調用該函數,用來向服務器發起連接請求 */
int connect(int sock_id, struct sockaddr *serv_sddr, int addrlen);
aock_id:通過哪個套接字來發送請求
serv_sttr:保存服務器的信息,也就是把連接請求發給ip和端口爲指定的那個服務器
addrlen:地址長度
<<成功:0 出錯:-1>>
【設置套接字的屬性】
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
level:指定控制套接字的層次.可以取三種值:
1)SOL_SOCKET:通用套接字選項.
2)IPPROTO_IP:IP選項.
3)IPPROTO_TCP:TCP選項.
optname:獲得或者是設置套接字選項
optval:爲套接字設置的參數的地址
optlen:爲套接字設置的參數的地址長度
//======實例==================
【允許重複綁定IP和端口的函數,照寫即可】
int i = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
【設定套接字定時阻塞】
struct timeval tv;
tv.tv_sec = 5; //設置5秒時間
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // 設置接收超時
accept();
==================================================================
struct fd_set rdfs;
struct timeval tv = {5 , 0}; // 設置5秒時間
FD_ZERO(&rdfs);
FD_SET(sockfd, &rdfs);
if (select(sockfd+1, &rdfs, NULL, NULL,
&tv) > 0) // socket就緒
recv() / recvfrom() // 從socket讀取數據
==================================================================
void handler(int signo) { return; }
signal(SIGALRM, handler);
alarm(5);
if(recv(,,,) < 0) ……
【心跳檢測】
爲了維持服務器和客戶端的之間的連接,客戶端每隔一個固定的時間(1-255秒)向服務器發送一個心跳包,
以表明客戶端和軟硬件運行正常,如果服務器在間隔時間內,沒有收到心跳包,則認爲客戶端出現問題,可以
根據相應情況進行處理。
將二進制IP轉換爲字符串並返回字符串的地址
char *inet_ntoa(sock_client.sin_addr);
測試程序:建立一個服務器,另建立一個客戶端登陸服務器,並向其發送數據,服務器端顯示收到的數據
步驟:
服務器端:(server)
No.1: 創建套接字並接收套接字描述符 socket
No.2: 配置服務器結構體信息 set:struct sockaddr
No.3: 綁定IP地址和端口到套接字 bind
No.4: 設置套接字爲監聽狀態 listen
No.5: 接收客戶端連接(阻塞)accept
No.6: 讀取數據並做相關的操作 read、recv
No.7: close(sock_fd);
客戶端:(client)
No.1: 創建套接字並接受套接字描述符socket
No.2: 配置服務器結構體信息set:struct sockaddr
No.3: 向客戶端發起連接請求 connect
No.4: 進行數據寫入操作 wirte、send
No.5: close(sock_fd);
+++++++UDP+++++++
(位於6-4的中間)
【sendto( ); 】
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
sockfd:套接字描述符
msg:指向要發送數據的指針
len:數據長度
flags:一般爲0
to:目的機的IP地址和端口號信息
fromlen:地址長度
<<成功:實際發送字節數 失敗:-1>>
【recvfrom( ); 】
int recvfrom (int sockfd, void *buf, int len, unsigned int flags, const struct sockaddr *from, int fromlen);
sockfd:套接字描述符
buf:存放接收數據的緩衝區
len:數據長度
flags:一般爲0
from:源主機的IP地址和端口號信息
fromlen:地址長度
<<成功:實際收到的字節數 失敗:-1>>
>>>>以上兩個函數只用於UDP協議
Connect 和 accept函數間會發生三次握手(3個空包)
第一次握手:client向server發送一個包(SYN包)TCP頭內的SYN=1, seq=一個隨機序列號x
客戶端處於SYN_SENT狀態
第二次握手:Server回覆一個包給client(SYN+ACK包)TCP頭內的SYN=1, ACK=1, seq= y, ack=x+1
服務端處於SYNf_RCVD狀態
第三次握手:ACK包(ACK=1, ack=y+1)
客戶端和服務端都處於ESTABUSHED
狀態,表示握手成功
爲什麼TCP是面向對象的,因爲它要進行三次握手
四次握手
Client向server發送一個請求關閉的包
Server給client一個回覆
Server告訴client已經沒有數據可發送了
Client給server回覆
內核和驅動幫我們完成阻塞的
阻塞I/O
·
非阻塞I/O
+++++++I/O多路複用+++++++
【lelect() 函數,實現自動監控文件描述符所對應的文件是否可寫、可讀......s】
int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
n:表示最大的文件描述符的值加1
read_fds:要關注的可讀(有數據可讀、有新的連接請求)的文件描述符的集合
write_fds:要關注的可寫的文件描述符的集合
except_fds:要關注的異常的文件描述符的集合
timeout:超時時間,如果在規定的時間內,沒有任何文件描述符準備好,則select直接返回6
//select調用一次,只能進行一次監控,如果要連續監控,則需使用循環
//select返回之後,不滿足條件的文件描述符對應的爲都被清零,所以如果要重新檢查,必須將fd重新加入集合
返回值:<0:出錯 ==0:超時 >0: 有文件描述符準備好(某些IO端口滿足可讀、可寫或異常的條件)
//select不會告訴用戶具體是哪一個IO準備好,需要程員自己來判斷
【timeout結構體】
struct timeval {
long tv_sec; /*秒數*/
long tv_usec; /*微秒數*/
};
在設置文件描述符時我們需要用到幾個宏
void FD_ZERO(fd_set *fdset);//把文件描述符集合中所有的位都清零
void FD_SET(int fd,fd_set *fdset);//將文件描述符fd加入到集合fdset中
void FD_CLR(int fd,fd_set *fdset);//將文件描述符從集合fdset中去除
int FD_ISSET(int fd,fd_set *fdset);//判斷描述符fd是否在集合fdset中,檢測集合中fd對應的位是否爲1
select編程步驟:
1:獲得文件描述符
int fd_serial = open("/dev/tty", O_RDWR);
int fd_socket = socket(AF_INET, SOCK_STREAM, 0);
bind(xxx);
listen(xxxx);
2:把所有關注的文件描述符加入到集合中
fd_set fdset;
FD_ZERO(&fdset);//對所有位進行清零
FD_SET(fd_serial, &fdset);
FD_SET(fd_socket, &fdset);
3:調用select進行監控
struct timeval tm = {5,0};
max_fd = fd_serial>fd_socket?fd_serial:fd_socket;
ret = select(max_fd +1 ,&fdset, NULL, NULL, &tm);
4:根據返回情況進行判斷
if(ret < 0)
{
perror("select");
exit(1)
}
if(ret == 0)
printf("說明在5秒之內,沒有任何IO準備好,超時\n!");
if(ret> 0) //有IO滿足可讀條件
{
if(FD_ISSET(fd_serial,&fdset))
read(fd_serial, buf, 20);//串口有數據可讀
if(FD_ISSET(fd_socket,&fdset))
conn_fd = accept(fd_socket, xxxxx); //有新的連接請求
}
其他相關函數:
struct hostent *gethostbyname(const char *name); //根據主機名或域名獲得主機信息
struct hostent {
char *h_name; /*官方主機名*/
char **h_aliases; /*主機的別名列表*/--->char *h_aliases[ ]={“host1”, “host1”};
int h_addrtype; /*主機的地址類型(IPv4 IPv6)*/
int h_length; /*地址長度*/
char **h_addr_list; /*主機的IP地址列表*/--->char *h_addr_list[ ] ={ip1, 1p2};
本質:解析文件 /etc/hosts
----------------------------------------------------------------------------------------------------------------
測試程序:用select創建一個類似於server的模型
編寫步驟:
Not1:創建一個鏈表並初始化;創建集合:fd_set read_fds; int max;
No.2:創建套接字並使套接字處於監聽狀態
No.3:先將max置爲sock_fd的值,然後進入while(1)進行select循環監控
while(1)
{
FD_ZERO(&read_fds); //對”集合“所有位清零
FD_SET(sock_fd, &read_fds); //將sock_fd加入”集合“
if(!(is_empty(head))
{
p = head->next;
遍歷鏈表將將每個鏈表節點放入”集合“
}
ret=select(max_fd+1, &read_fds, NULL, NULL, NULL);//調用select開始監控
ret<0:--->perror
if(FD_ISSET(sock_fd, &read_fds))
Accept:進行客戶端連接 並將client_fd放入鏈表
if(!is_empty(head))
{
p=head37_->next; if(p!=NULL);
if(FD_ISSET(p->client, &read_fds)); //如果某client有數據過來了,則集合中相應的位爲1
read(); 返回值爲0說明客戶端調用了close(sock_fd)關閉了套接字
從鏈表中關閉文件描述符並關閉該文件
否則輸出接收到的內內容
p=p->next;
}
}
No.4:close(sock_fd);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.