Linux C網絡編程學習#1
網絡編程的原理
兩台計算機的通信本質上是通過物理線路相連接,但通信的問題在於怎麼解讀這些電信號?這就從一個硬件的問題轉向了軟件的問題了。因此需要規定兩台計算機如何解讀相互傳輸的電信號,這種規定如何解讀另一台計算機的電信號(或報文)的程序就叫協議。
現代網絡普遍基於TCP/IP協議族和OSI協議族來實現計算機與計算機間的交流,由於博客園中已有很多相關介紹,本文在這不過多介紹。
Linux 網絡編程的接口
1. htonl函數和htons函數
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostlong)
意義: 由於存儲兩個字節組成的整數有兩種方式(大端和小端),因此不同操作系統對一整數的存儲和解讀是不同的,由此我們需要確保所輸入的整數與對方所讀取的整數是一致的。
功能: htonl函數可用於將主機字節順序中的IPv4 地址轉換為網絡字節順序中的IPv4地址。
實驗代碼:
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
int a = 0x12345678;
short b = 0x1234;
printf("%#x\n", htonl(a));
printf("%#x\n", htons(b));
return 0;
}
2. inet_ntop函數
int inet_ntop(int af, const char * src, void * dst);
功能:用於將用整數表示的IPV4數據轉換成用字符串表示的IPV4數據。
實驗代碼:
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
// unsigned char的大小為1個字節,而int的大小為4個字節
// 因此可以用4個unsigned char的變量去表達一個int的值。
// 這樣做的好處是,由於IPV4是由4個字節組成的,因而可以用unsigned char分別輸入4個字節,然後存儲成一個整數。
unsigned char ip_int[] = {192, 168, 3, 103};
char ip_str[16] = ""; // "192.168.3.103"
// 將一個表示IPV4的整數轉換成一個表示IPV4的字符串。
inet_ntop(AF_INET, &ip_int, ip_str, 16);
printf("ip_s = %s\n", ip_str);
return 0;
}
3. inet_pton函數
const char * inet_ntop(int af, const void * src, char * dst, socklen_t size);
功能: 與inet_ntop功能相反。
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
char ip_str[] = "192.168.3.103";
unsigned int ip_int = 0;
unsigned char * ip_p = NULL;
// 將點分十進制的IP地址轉化為無符號整數數據。
inet_pton(AF_INET, ip_str, &ip_int);
printf("ip_int = %d\n", ip_int);
ip_p = (unsigned char *) &ip_int;
printf("in_unit = %d.%d.%d.%d\n", *ip_p, *(ip_p + 1), *(ip_p + 2), *(ip_p + 3));
return 0;
}
4. socket 函數
int socket(int domain, int type, int protocol);
功能: 創建套接字
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
// 使用Socket函數創建套接字。
// 用於UDP網絡編程的套接字。
// socket函數是一個創建套接字的函數接口,其返回值是一個整數,用於表示文件描述符。
int sockfd;
if ((sockfd=socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("failed to create a socket");
exit(1);
}
// sockfd 的值是3,因為socket是一種文件描述符,而在運行C時,編釋器會創建3個文件描述符,分別為標準輸入,標準輸出,標準錯誤輸出。
printf("sockfd=%d\n", sockfd);
return 0;
}
4. bind 函數
功能: 綁定IP和端口。
#include <netinet/in.h>
#include <string.h>
int main(int argc, char *argv[]) {
// 0. 寫成通用命令行程序
if (argc < 3) {
fprintf(stderr, "Usage %s ip port", argv[0]);
exit(0);
}
// 1. 創建套接字。
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 1.1 測試socket配置是否成功。
if (sockfd == -1) {
perror("[-] failed to create socket! [-]");
exit(1);
}
// 2. 將網絡信息寫入結構體中
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET; // IPV4
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
// 3. 將網絡信息與套接字綁定
int err = bind(sockfd, (const struct sockaddr *) &serveraddr, sizeof(serveraddr));
if (err == -1) {
perror("failed to bind ip");
exit(1);
}
return 0;
}
- recvform函數
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
功能: 接受UDP客戶端發的信息
#include <stdio.h>
#include <arpa/inet.h> // inet_addr htons
#include <sys/socket.h> // socket
#include <sys/types.h> // AF_INET
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MESSAGE_BUF_MAX 200
int main(int argc, char *argv[]) {
// 0. 寫成通用的命令行程序
if (argc < 3) {
fprintf(stderr, "Usage %s ip port", argv[0]);
}
// 1. 創建套接字。
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 1.1 測試套接字的配置是否成功。
if (sockfd == -1) {
perror("failed to create socket");
exit(1);
}
// 2. 將網絡信息寫人結構體
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
// 3. 綁定IP
int err = bind(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));
// 3.1 測試IP是否綁定成功
if (err == -1) {
perror("failed to bind ip");
exit(1);
}
char buf[MESSAGE_BUF_MAX];
struct sockaddr_in clientaddr;
socklen_t addrlen = sizeof(struct sockaddr_in);
// 4. 接受信息
while (true) {
err = recvfrom(sockfd, buf, MESSAGE_BUF_MAX, 0, (const struct sockaddr *) &clientaddr, &addrlen);
if (err == -1) {
perror("failed to receive the client message");
exit(1);
}
// 打印Client的網絡信息
printf("ip:%s, port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
// 打印Client發出的Message
puts(buf);
putchar('\n');
}
return 0;
}
- sendto 函數
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
cat main.c
#include <stdio.h> // printf fgets
#include <stdlib.h> // exit
#include <unistd.h> // close
#include <string.h> // strlen
#include <stdbool.h>
#include <arpa/inet.h> // inet_addr htons
#include <sys/types.h> // AF_INET
#include <sys/socket.h> // socket
#include <netinet/in.h> // sockaddr_in
#define MESSAGE_BUF_MAX 200
int main(int argc, char *argv[]) {
// 寫一個通用的終端程序
if (argc < 3) {
fprintf(stderr, "Usage: %s ip port\n", argv[0]);
exit(1);
}
// 1. 創建套接字IPV4, UDP
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 1.1 測試套接字是否配置成功
if (sockfd == -1) {
perror("[-]\tfailed to create socket![-]");
exit(0);
}
// 2. 配置目標主機的IP和PORT。
struct sockaddr_in destination_address;
socklen_t addrlen = sizeof(destination_address);
destination_address.sin_family = AF_INET; // IPV4
destination_address.sin_addr.s_addr = inet_addr(argv[1]); // ip 地址,由於要將IP轉成無符號整數類型用於儲存,因此使用inet_addr函數轉換。
destination_address.sin_port = htons(atoi(argv[2])); // sin_port 的類型本質是unsigned short int,其sizeof為2Byte,而又因為不知道其他主機的操作系統是小端模式,還是大端模式,因而使用htons做轉換。
// 3. 使用sendto函數向UDP伺服器發送數據。
// 由於UDP是無連接的,因此無需在發送數據前先建立連接。
char buf[MESSAGE_BUF_MAX];
while (true){
fgets(buf, MESSAGE_BUF_MAX, stdin);
buf[strlen(buf)-1] = '\0';
if (sendto(sockfd, buf, MESSAGE_BUF_MAX, 0, (struct sockaddr *) &destination_address, addrlen) == -1) {
perror("[-]\tconnect error\t[-]\n");
exit(1);
}
}
// 關閉套接字文件。
close(sockfd);
return 0;
}