基于Linux下C語言的Socket網(wǎng)絡(luò)編程
網(wǎng)絡(luò)上的兩個程序通過一個雙向的通信連接實現(xiàn)數(shù)據(jù)的交換,這個連接的一端稱為一個socket狐蜕。
Socket被廣泛用作網(wǎng)絡(luò)通信,它幾乎支持所有的編程語言芭商,各種語言對于Socket操作流程也比較類似语盈。
服務(wù)端程序的創(chuàng)建流程為創(chuàng)建socket——綁定端口號——監(jiān)聽——接受連接——讀和寫哑了;
客戶端程序的創(chuàng)建流程為創(chuàng)建socket——通過IP和端口連接服務(wù)端——讀和寫。
[TOC]
服務(wù)端
1. 創(chuàng)建Socket
包含頭文件:
#include <sys/socket.h>
#include <sys/types.h>
函數(shù)原型:int socket(int domain, int type, int protocol);
socket
函數(shù)里有三個參數(shù)烧颖。
domain
選擇通信協(xié)議族弱左,常用的有以下幾種。
Name | Purpose |
---|---|
AF_UNIX, AF_LOCAL | Local communication |
AF_INET(常用) | IPv4 Internet protocols |
AF_INET6 | IPv6 Internet protocols |
type
指定Socket類型炕淮,常用以下幾種拆火。
Name | Purpose |
---|---|
SOCK_STREAM | 流式套接字(TCP協(xié)議) |
SOCK_DGRAM | 數(shù)據(jù)報式套接字(UDP協(xié)議) |
protocol指定協(xié)議,常用以下幾種
NAME | Purpose |
---|---|
IPPROTO_TCP | TCP傳輸協(xié)議 |
IPPROTO_UDP | UDP傳輸協(xié)議 |
IPPROTO_STCP | STCP傳輸協(xié)議 |
IPPROTO_TIPC | TIPC傳輸協(xié)議 |
type和protocol不可以隨意組合涂圆,當?shù)谌齻€參數(shù)type
為 0
自動選擇第二個參數(shù)對應(yīng)的默認協(xié)議
Socket如果創(chuàng)建成功们镜,則返回一個描述該網(wǎng)絡(luò)通信端點的文件描述符,操作系統(tǒng)會自動分配當前最小可用的文件描述符润歉。
示例代碼
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
2. 綁定端口號和IP地址
包含頭文件:
#include <sys/socket.h>
#include <sys/types.h>
函數(shù)原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind()
函數(shù)把地址族中的特定地址賦給socket模狭。
sockfd
socket描述字,通過socket創(chuàng)建踩衩,標識唯一一個服務(wù)端描述字嚼鹉。
sockaddr
結(jié)構(gòu)體,通過初始化sockaddr_in
結(jié)構(gòu)體然后進行強制類型轉(zhuǎn)化九妈。示例代碼如下:
struct sockaddr_in *server_socket = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
memset(server_socket, 0, sizeof (struct sockaddr_in)); // 清空sockaddr_in 結(jié)構(gòu)體
server_socket->sin_addr.s_addr = htonl(INADDR_ANY); // 任意IP地址
server_socket->sin_family = AF_INET; // 使用IPv4協(xié)議族
server_socket->sin_port = htons(PORT); // 端口號
addrlen
為sockaddr
的長度反砌。
bind示例代碼如下
bind(sockfd, (struct sockaddr *)server_socket, sizeof (struct sockaddr));
3. 連接和監(jiān)聽
包含頭文件:
#include <sys/socket.h>
#include <sys/types.h>
函數(shù)原型int listen(int sockfd, int backlog);
sockfd
為服務(wù)端建立socket的文件描述符
backlog
為對應(yīng)socket可以排隊的最大連接數(shù)
服務(wù)端調(diào)用listen()
函數(shù)監(jiān)聽socket
,當客戶端通過connect()
函數(shù)連接服務(wù)端萌朱,發(fā)送連接請求時宴树,listen()
就會監(jiān)聽到這個請求。
4. 接受客戶端連接
包含頭文件:
#include <sys/socket.h>
#include <sys/types.h>
函數(shù)原型int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd
為服務(wù)端打開的socket描述字
addr
用于保存連接的客戶端的IP地址和端口號晶疼,其內(nèi)容與sockaddr結(jié)構(gòu)體類似酒贬。
addrlen
是接受地址的長度。
accept()
函數(shù)如果執(zhí)行成功翠霍,則返回一個由內(nèi)核生成的全新的套接字锭吨,用于和新客戶端之間通信。
5. 讀和寫
包含頭文件:
#include <unistd.h>
函數(shù)原型:ssize_t read(int fd, void *buf, size_t count);
函數(shù)原型:ssize_t write(int fd, const void *buf, size_t count);
當socket打開網(wǎng)絡(luò)描述字后寒匙,程序可以像讀寫文件一樣向網(wǎng)絡(luò)描述字讀或者寫零如,對應(yīng)接受和發(fā)送數(shù)據(jù)。
客戶端
1. 創(chuàng)建socket
過程同服務(wù)端锄弱,socket返回的套接字描述符將直接用于和對端通信考蕾。
2. 連接服務(wù)器
函數(shù)原型int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
為客戶端建立socket的文件描述符
addr
為sockaddr結(jié)構(gòu)體保存的服務(wù)端的IP地址和端口號,以及協(xié)議類型会宪,例如:
struct sockaddr_in *server_addr = (struct sockaddr_in *)malloc(sizeof (struct sockaddr_in));
memset(server_addr, 0, sizeof (struct sockaddr_in));
server_addr->sin_family = AF_INET; // 使用IPv4協(xié)議族
server_addr->sin_port = htons(port); // port為服務(wù)器綁定的端口號
inet_pton(AF_INET, "xxx.xxx.xxx.xxx", &server_addr->sin_addr); // 服務(wù)端的IP地址
客戶端發(fā)起tcp連接請求肖卧,服務(wù)端監(jiān)聽到請求并接受請求后,TCP連接建立即可以開始傳輸文件掸鹅。(如果使用UDP協(xié)議有些許區(qū)別塞帐,暫時不做討論拦赠。)
3.發(fā)送數(shù)據(jù)
連接建立后,客戶端通過write()
和write()
函數(shù)向打開的sockfd發(fā)送或接受數(shù)據(jù)
代碼示例
Server
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORT 8000
int main()
{
// 創(chuàng)建socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 申請服務(wù)端和客戶端地址結(jié)構(gòu)體空間葵姥,客戶端地址用于保存新連接的地址端口信息
struct sockaddr_in *server_socket = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
struct sockaddr_in *client_socket = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
socklen_t client_address_len;
memset(server_socket, 0, sizeof (struct sockaddr_in));
memset(client_socket, 0, sizeof (struct sockaddr_in));
server_socket->sin_addr.s_addr = htonl(INADDR_ANY);
server_socket->sin_family = AF_INET;
server_socket->sin_port = htons(PORT);
// 綁定
bind(sockfd, (struct sockaddr *)server_socket, sizeof (struct sockaddr));
// 監(jiān)聽
listen(sockfd, 20);
// 接受
int connect_fd = accept(sockfd, (struct sockaddr *)client_socket, &client_address_len);
char buf[] = "Hello Wrold!";
// 發(fā)送
while(write(connect_fd, buf, strlen(buf)))
{
printf("send msg: %s\n", buf);
sleep(1);
}
return 0;
}
Client
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// 創(chuàng)建socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 準備地址結(jié)構(gòu)體
struct sockaddr_in *server_addr = (struct sockaddr_in *)malloc(sizeof (struct sockaddr_in));
memset(server_addr, 0, sizeof (struct sockaddr_in));
server_addr->sin_family = AF_INET;
server_addr->sin_port = htons(8000);
inet_pton(AF_INET, "127.0.0.1", &server_addr->sin_addr);
// 連接
connect(sockfd, (struct sockaddr *)server_addr, sizeof (struct sockaddr)); // sockaddr_in強制轉(zhuǎn)換成sockaddr
char buf[1024] = "";
// 讀取
while(read(sockfd, buf, 1024))
{
printf("recv msg: %s\n", buf);
memset(buf, 0, 1024);
}
return 0;
}