最近重温網絡的時候,突然發現,底層就那麼些接口,java肯定也是封裝了底層接口,看過我前面Nio相關的小夥伴肯定知道對這些類有點影響(Buffer,Channel,Selector,SelectionKey),可是跟底層對應不起來啊,這一篇就透過源碼看一下,大概能幫助你更好的瞭解這幾個類,及底層的實現。偏重個人興趣向整理,如有不適,歡迎吐槽
Linux網絡編程
查閲資料的時候,發現wiki百科講的已經十分好了,我先貼下原文Berkeley套接字,相當完美的描述了Socket相關Api介紹及demo演示,由於,大學學的c語言都快還給老師了,寫個demo已然不太現實,這裏就臭不要臉的套用wiki百科的demo,c語言講解部分,如果有錯誤歡迎指出~~。
下面是精簡版本的linux網絡編程,詳細版可以參考鏈接。
LinuxAPI
socket()
-
socket()創建一個新的確定類型的套接字,返回套接字。- api:
int socket(int domain, int type, int protocol); -
參數:
domain: 為創建的套接字指定協議集 eg. IPV4type: socket類型 eg. 流,數據報文-
protocol: 實際傳輸協議 eg. TCP,UDPbind()
- api:
-
bind()為一個套接字分配地址。當使用socket()創建套接字後,只賦予其所使用的協議,並未分配地址。在接受其它主機的連接前,必須先調用bind()為套接字分配一個地址。- api:
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen); -
參數:
sockfd:套接字描述符,上面返回的套接字my_addr: 指向sockaddr結構(用於表示所分配地址)的指針-
addrlen: 用socklen_t字段指定了sockaddr結構的長度listen()
- api:
-
listen()當socket和一個地址綁定之後,listen()函數會開始監聽可能的連接請求。然而,這隻能在有可靠數據流保證的時候使用,例如:數據類型(SOCK_STREAM,SOCK_SEQPACKET)。- api:
int listen(int sockfd, int backlog); -
參數:
sockfd: 套接字描述符,上面返回的套接字backlog: 完成三次握手、等待accept的全連接的隊列的最大長度上限。
- api:
accept()
-
accept()當應用程序監聽來自其他主機的面對數據流的連接時,通過事件(比如Unix select()系統調用)通知它。必須用accept()函數初始化連接。 Accept() 為每個連接創立新的套接字並從監聽隊列中移除這個連接。它使用如下參數:- api:
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); -
參數:
sockfd:監聽的套接字描述符cliaddr: 指向sockaddr 結構體的指針,客户機地址信息。addrlen:指向socklen_t的指針,確定客户機地址結構體的大小 。
- api:
connect()
-
connect()系統調用為一個套接字設置連接,參數有文件描述符和主機地址。鏈接到指定地址- api:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); -
參數:
sockfd:監聽的套接字描述符serv_addr: 指向sockaddr 結構體的指針,服務器地址信息。addrlen:指向socklen_t的指針,確定服務器地址結構體的大小 。
- api:
select()
-
select():在一段指定的時間內,監聽用户感興趣的文件描述符上可讀、可寫和異常等事件- api:
int select (int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout); -
參數:
nfds:沒有用,僅僅為與伯克利Socket兼容而提供。readfds:指定一個Socket數組,select檢查該數組中的所有Socket。如果成功返回,則readfds中存放的是符合‘可讀性’條件的數組成員(如緩衝區中有可讀的數據)。writefds:指定一個Socket數組,select檢查該數組中的所有Socket。如果成功返回,則writefds中存放的是符合‘可寫性’條件的數組成員(包括連接成功)。exceptfds:指定一個Socket數組,select檢查該數組中的所有Socket。如果成功返回,則cxceptfds中存放的是符合‘有異常’條件的數組成員(包括連接接失敗)。timeout:指定select執行的最長時間,如果在timeout限定的時間內,readfds、writefds、exceptfds中指定的Socket沒有一個符合要求,就返回0。
- api:
poll()
-
poll()用於檢查套接字的狀態。 套接字可以被測試,看是否可以寫入、讀取或是有錯誤。- api:
int poll(struct pollfd * fds , nfds_t nfds , int timeout ); -
參數:
fds是pollfd結構體指針nfdsnfds是描述符個數,結構體pollfd數組元素的個數timeout:參數設置為-1時,表示永遠阻塞等待。0表示立即返回,不阻塞。大於0時,表示等待指定數目的毫秒數。
- api:
fcntl()
-
fcntl()打開文件描述符,具體操作由cmd決定- api:
int fcntl(int fd , int cmd , ... /* arg */ ); -
參數
fd:文件描述符cmd:操作指令
- api:
epoll_create()
-
epoll_create()在內核中創建epoll實例並返回一個epoll文件描述符。- api:
int epoll_create(int size); -
參數:
size:而現在 size 已經沒有這種語義了,但是調用者調用時 size 依然必須大於 0,以保證後向兼容性。
- api:
epoll_ctl()
-
epoll_ctl()向 epfd 對應的內核epoll實例添加、修改或刪除對 fd 上事件 event 的監聽。- api:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); -
參數
epfdepoll結構體opcud對應的事件枚舉fd文件描述符events水平觸發or邊緣觸發
- api:
epoll_wait()
-
epoll_wait()等待其管理的連接上的 IO 事件- api:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); -
參數
epfdepoll結構體eventsepoll_event結構體指針maxevents最多返回多少事件timeout當 timeout 為 0 時,epoll_wait 永遠會立即返回。而 timeout 為 -1 時,epoll_wait 會一直阻塞直到任一已註冊的事件變為就緒。當 timeout 為一正整數時,epoll 會阻塞直到計時 timeout 毫秒終了或已註冊的事件變為就緒。
- api:
send()和recv(),或者write()和read(),或者recvfrom()和sendto()等
- 用於往/從遠程套接字發送和接受數據
相關api大致如上,如有更精細的可以自行搜索。
demo
BIO
/* Server code in C */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
struct sockaddr_in stSockAddr;
int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(-1 == SocketFD)
{
perror("can not create socket");
exit(EXIT_FAILURE);
}
memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(1100);
stSockAddr.sin_addr.s_addr = INADDR_ANY;
if(-1 == bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
{
perror("error bind failed");
close(SocketFD);
exit(EXIT_FAILURE);
}
if(-1 == listen(SocketFD, 10))
{
perror("error listen failed");
close(SocketFD);
exit(EXIT_FAILURE);
}
for(;;)
{
int ConnectFD = accept(SocketFD, NULL, NULL);
if(0 > ConnectFD)
{
perror("error accept failed");
close(SocketFD);
exit(EXIT_FAILURE);
}
/* perform read write operations ... */
shutdown(ConnectFD, SHUT_RDWR);
close(ConnectFD);
}
close(SocketFD);
return 0;
}
/* Client code in C */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
struct sockaddr_in stSockAddr;
int Res;
int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (-1 == SocketFD)
{
perror("cannot create socket");
exit(EXIT_FAILURE);
}
memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(1100);
Res = inet_pton(AF_INET, "192.168.1.3", &stSockAddr.sin_addr);
if (0 > Res)
{
perror("error: first parameter is not a valid address family");
close(SocketFD);
exit(EXIT_FAILURE);
}
else if (0 == Res)
{
perror("char string (second parameter does not contain valid ipaddress");
close(SocketFD);
exit(EXIT_FAILURE);
}
if (-1 == connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
{
perror("connect failed");
close(SocketFD);
exit(EXIT_FAILURE);
}
/* perform read write operations ... */
shutdown(SocketFD, SHUT_RDWR);
close(SocketFD);
return 0;
}
NIO
#include<stdio.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<ctype.h>
#define MAXLEN 1024
#define SERV_PORT 8000
#define MAX_OPEN_FD 1024
int main(int argc,char *argv[])
{
int listenfd,connfd,efd,ret;
char buf[MAXLEN];
struct sockaddr_in cliaddr,servaddr;
socklen_t clilen = sizeof(cliaddr);
struct epoll_event tep,ep[MAX_OPEN_FD];
listenfd = socket(AF_INET,SOCK_STREAM,0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
listen(listenfd,20);
// 創建一個epoll fd
efd = epoll_create(MAX_OPEN_FD);
tep.events = EPOLLIN;tep.data.fd = listenfd;
// 把監聽socket 先添加到efd中
ret = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);
// 循環等待
for (;;)
{
// 返回已就緒的epoll_event,-1表示阻塞,沒有就緒的epoll_event,將一直等待
size_t nready = epoll_wait(efd,ep,MAX_OPEN_FD,-1);
for (int i = 0; i < nready; ++i)
{
// 如果是新的連接,需要把新的socket添加到efd中
if (ep[i].data.fd == listenfd )
{
connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
tep.events = EPOLLIN;
tep.data.fd = connfd;
ret = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
}
// 否則,讀取數據
else
{
connfd = ep[i].data.fd;
int bytes = read(connfd,buf,MAXLEN);
// 客户端關閉連接
if (bytes == 0){
ret =epoll_ctl(efd,EPOLL_CTL_DEL,connfd,NULL);
close(connfd);
printf("client[%d] closed\n", i);
}
else
{
for (int j = 0; j < bytes; ++j)
{
buf[j] = toupper(buf[j]);
}
// 向客户端發送數據
write(connfd,buf,bytes);
}
}
}
}
return 0;
}
極度精簡版本
nio已經很精簡了就不多廢話了
server
int main(void)
{
struct sockaddr_in stSockAddr;
//創建套接字
int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//結構體初始化
memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(1100);
stSockAddr.sin_addr.s_addr = INADDR_ANY;
//綁定地址
bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in));
//監聽請求
listen(SocketFD, 10);
//接受請求
int ConnectFD = accept(SocketFD, NULL, NULL);
//do something
//關閉
close(ConnectFD);
//關閉
close(SocketFD);
return 0;
}
client
int main(void)
{
struct sockaddr_in stSockAddr;
int Res;
int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(1100);
//IP地址轉換
Res = inet_pton(AF_INET, "192.168.1.3", &stSockAddr.sin_addr);
//連接地址
connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in))
shutdown(SocketFD, SHUT_RDWR);
close(SocketFD);
return 0;
}
參考文章
Berkeley套接字:https://zh.wikipedia.org/wiki...
epoll:https://zh.wikipedia.org/wiki...
linux文檔:https://man7.org/linux/man-pa...