我們這次要完成的最終結(jié)果如上圖所示
前置知識(shí)
- C語言
-
Linux Socket
編程 - 基本的網(wǎng)絡(luò)知識(shí)
-
Unix/Linux
基本知識(shí)
一圖勝千言皇型,可以看出Socket
編程主要分為這7個(gè)步驟滤蝠,這次我們主要編寫服務(wù)器端的代碼,客戶端由瀏覽器代理上沐。
網(wǎng)絡(luò)層的
IP協(xié)議
使用IP地址
唯一的標(biāo)識(shí)了一臺(tái)主機(jī),而傳輸層的協(xié)議使用協(xié)議名+端口號
唯一的標(biāo)識(shí)了系統(tǒng)的一個(gè)進(jìn)程,所以我們才可以利用socket
在不同主機(jī)的進(jìn)程間通信
創(chuàng)建一個(gè)socket
int socket(int domain, int type, int protocol);
這是創(chuàng)建socket的函數(shù)原型
domain:
中文意思為域,可傳的值為AF_UNIX
裤唠、AF_LOCAL
、AF_INET
预鬓,AF
意為Adress Family
巧骚。前兩個(gè)為本機(jī)操作,最后一個(gè)為IPv4
的網(wǎng)絡(luò)操作格二,所以為AF_INET
type:
類型,可傳值為SOCK_STREAM
竣蹦、SOCK_DGRAM
顶猜、SOCK_PACKET
等
SOCK_STREAM
使用 TCP 協(xié)議
傳輸數(shù)據(jù),SOCK_DGRAM
使用 UDP 協(xié)議
傳輸數(shù)據(jù)痘括,我們要做的是Web服務(wù)器
长窄,肯定是選擇面向連接的可靠的TCP協(xié)議
,所以這個(gè)值傳SOCK_STREAM
protocol:
所用的協(xié)議纲菌,有IPPROTO_TCP
挠日、IPPTOTO_UDP
、IPPROTO_SCTP
翰舌,傳0為自動(dòng)選擇協(xié)議嚣潜,所以我們傳0
返回值:
返回一個(gè)socket描述符(socket descriptor),它唯一標(biāo)識(shí)一個(gè)socket椅贱,這個(gè)socket描述字跟文件描述字一樣懂算。
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
將socket和地址綁定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket
文件描述符只冻,socket()
函數(shù)的返回值,也就是server_socket
addr:
指向地址結(jié)構(gòu)體的指針计技,這是一個(gè)struct sockaddr
類型的通用指針喜德,我們實(shí)際創(chuàng)建的結(jié)構(gòu)體為
struct sockaddr_in {
__uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
傳遞的時(shí)候需要做強(qiáng)制類型轉(zhuǎn)換
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
注意這里的網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序的轉(zhuǎn)換,INADDR_ANY
表示任何網(wǎng)絡(luò)地址都可以訪問
memset
函數(shù)初始化server_addr
各個(gè)字節(jié)為0垮媒,防止有未初始化的垃圾值存在
addrlen:
結(jié)構(gòu)體的長度舍悯,由于在函數(shù)內(nèi)部無法獲取到結(jié)構(gòu)體長度(因?yàn)閭鬟f的是指針,參考數(shù)組)睡雇,所以需要把長度傳入
返回值:
綁定成功或者失敗的消息碼萌衬,暫時(shí)不作處理
bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
監(jiān)聽
int listen(int sockfd, int backlog);
sockfd:
socket
文件描述符,socket()
函數(shù)的返回值入桂,也就是server_socket
backlog:
socket
待連接隊(duì)列的最大個(gè)數(shù)奄薇,一般為5
返回值:
綁定成功或者失敗的消息碼,暫時(shí)不作處理
listen(server_socket, 5);
與客戶端建立連接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:
socket
文件描述符抗愁,socket()
函數(shù)的返回值馁蒂,也就是server_socket
addr:
客戶端地址信息的結(jié)構(gòu)體,不關(guān)心可以傳NULL
addrlen:
客戶端地址長度蜘腌,不關(guān)心可以傳NULL
返回值:
socket
文件描述符沫屡,在與客戶端建立連接后,accpet
還是會(huì)生成一個(gè)專門用于和當(dāng)前客戶端通信的socket
撮珠,而原來那個(gè)socket
照常負(fù)責(zé)和其他等待建立連接的客戶端建立通信
int client_socket = accept(server_socket, NULL, NULL);
從瀏覽器讀取請求內(nèi)容
ssize_t read(int fd, void *buf, size_t count);
fd:
文件描述符沮脖,從哪個(gè)文件讀
buf:
讀的內(nèi)容存到buf中
count:
共讀多少個(gè)字節(jié)
char buf[1024];
read(client_socket, buf, 1024);
記住,在Linux
芯急,一切皆文件勺届,網(wǎng)絡(luò)接口、甚至鼠標(biāo)鍵盤顯示器都是文件
往瀏覽器寫響應(yīng)內(nèi)容
ssize_t write(int fd, const void *buf, size_t count);
fd:
文件描述符娶耍,往哪個(gè)文件寫
buf:
內(nèi)容的首地址
count:
共讀多少個(gè)字節(jié)
char status[] = "HTTP/1.0 200 OK\r\n";
char header[] = "Server: DWBServer\r\nContent-Type: text/html;charset=utf-8\r\n\r\n";
char body[] = "<html><head><title>C語言構(gòu)建小型Web服務(wù)器</title></head><body><h2>歡迎</h2><p>Hello免姿,World</p></body></html>";
write(client_socket, status, sizeof(status));
write(client_socket, header, sizeof(header));
write(client_socket, body, sizeof(body));
寫的格式是按HTTP
協(xié)議響應(yīng)報(bào)文的格式寫的,響應(yīng)報(bào)文的格式為響應(yīng)行+響應(yīng)首部+響應(yīng)體
榕酒,注意響應(yīng)首部
和響應(yīng)體
之間有一個(gè)空行
在瀏覽器中輸入http://localhost:8080/胚膊, 就會(huì)出現(xiàn)
用Charles抓包
關(guān)閉連接
int close(int fd);
close(client_socket);
close(server_socket);
最后把兩個(gè)socket
全部關(guān)閉
完整代碼
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#define PORT 8080 // 服務(wù)器監(jiān)聽端口
int main(){
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(server_socket, 5);
int client_socket = accept(server_socket, NULL, NULL);
char buf[1024];
read(client_socket, buf, 1024);
printf("%s",buf);
char status[] = "HTTP/1.0 200 OK\r\n";
char header[] = "Server: DWBServer\r\nContent-Type: text/html;charset=utf-8\r\n\r\n";
char body[] = "<html><head><title>C語言構(gòu)建小型Web服務(wù)器</title></head><body><h2>歡迎</h2><p>Hello,World</p></body></html>";
write(client_socket, status, sizeof(status));
write(client_socket, header, sizeof(header));
write(client_socket, body, sizeof(body));
close(client_socket);
close(server_socket);
return 0;
}
結(jié)語
至此想鹰,我們已經(jīng)用socket
實(shí)現(xiàn)了一個(gè)最簡單的Web服務(wù)器
(其實(shí)還算不上紊婉,只是一個(gè)瀏覽器充當(dāng)client
的socket
小程序),下一篇繼續(xù)完善這個(gè)Web服務(wù)器
辑舷,加入處理Get
請求的邏輯喻犁,進(jìn)一步實(shí)現(xiàn)HTTP
協(xié)議