套接字編程

+++++++套接字編程+++++++

(位於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);


發佈了49 篇原創文章 · 獲贊 28 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章