一:理解网络编程和套接字

文章详细介绍了网络编程中套接字的基本概念,如创建服务器和客户端套接字,以及在Linux和Windows平台上使用套接字进行通信的方法,特别关注了Winsock的初始化和清理。
摘要由CSDN通过智能技术生成

书中源码自取

https://gitee.com/pipe-man/tcp_ip_socket/tree/master/%E6%BA%90%E4%BB%A3%E7%A0%81

1.1 理解网络编程和套接字

网络编程中接受连接请求的套接字创建过程可整理如下:
#include <sys/socket.h> (以下函数:成功返回0或文件描述符,失败返回-1)

  • 构建接电话套接字
    1. 调用socket函数创建套接字 int socket(int domain, int type, int protocol)
    2. 调用bind函数分配IP和端口号 int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen)
    3. 调用listen函数转为可接受请求状态 int listen(int sockfd, int backlog)
    4. 调用accept函数受理连接请求 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void ErrHandling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    int serv_sock;
    int client_sock;

    struct sockaddr_in serv_addr;
    struct sockaddr_in client_addr;
    socklen_t client_addr_size;

    char message[] = "hello world!";

    if (argc != 2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    //1.调用socket函数创建套接字
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1) {
        ErrHandling("socket() err");
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //主机字节序转网络字节序
    serv_addr.sin_port = htons(atoi(argv[1]));

    //2.调用bind函数分配ip和port
    if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
        ErrHandling("bind() error");
    }

    //3.调用listen将套接字转为可接收状态
    if (listen(serv_sock, 5) == -1) {
        ErrHandling("listen() error");
    }

    client_addr_size = sizeof(client_addr);
    //4.调用accept受理连接请求
    client_sock = accept(serv_sock, (struct sockaddr*)&client_addr, &client_addr_size);
    if (client_sock == -1) {
        ErrHandling("accept() error");
    }

    //传输数据
    write(client_sock, message, sizeof(message));
    close(client_sock);
    close(serv_sock);
    return 0;
}

  • 构建打电话套接字
    1. 调用socket int socket(int domain, int type, int protocol)
    2. 调用connect()发送连接请求 int connect(int sockfd, struct sockaddr* serv_addr, socklen_t addr_len);
//client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void ErrHandling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    int sock;
    struct sockaddr_in serv_addr;
    char message[30];
    int str_len;

    if (argc != 3) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        ErrHandling("socket() error");
    }

    memset(&serv_addr, 0, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
        ErrHandling("connect() error");
    }

    str_len = read(sock, message, sizeof(message)-1);
    if (str_len == -1) {
        ErrHandling("read() error");
    }
    
    printf("Message from server : %s \n", message);
    close(sock);
    return 0;
}

运行结果如下
服务端
客户端

注意:由于没有开启端口复用,所以运行完成无法再次立即运行

1.2 基于linux的文件操作

linux中,socket也是文件的一种,所以可以使用文件IO的相关函数

分配给标准输入输出及错误的文件描述符
0 标准输入:Standard Input
1 标准输出:Standard Output
2标准错误:Standard Error

打开文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/**
* @brief: open
* @param[1]:文件的地址
* @param[2]: 文件的打开模式
* @return:成功返回文件描述符,失败返回-1
*/
int open(const char* path, int flag)
文件打开模式
打开模式 含义
O_CREAT 必要时创建文件
O_TRUNC 删除全部现有数据
O_APPEND 维持现有数据,保存到后面
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写

关闭文件

#include <unistd.h>

/**
* @brief:close
* @param:文件描述符
* @return:成功返回0,失败-1
*/
int close(int fd);

将数据写入文件

linux不区分文件与套接字,所以通过套接字向其他PC发送数据也用此函数

#include <unistd.h>
/**
* @param[1]:文件描述符
* @param[2]:要传输数据的地址
* @param[3]: 传输数据的字节数
* @return 是通过typedef声明的signed int
*/
ssize_t write(int fd, const void* buf, size_t nbytes)

读取数据

#include <unistd.h>
/**
*param[3]:要接受数据的最大字节数
*/
ssize read(int fd, void* buf, size_t nbytes);
  • demo
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

const int kBufferSize = 100;

void ErrHandling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main() {
    int fd;
    char buf[] = "hello World!\n";

    fd = open("./data.txt",  O_RDWR | O_TRUNC);
    if (fd == -1) {
        ErrHandling("open() error");
    }
    printf("file descriptor: %d\n", fd);
    printf("buf data: %s \n", buf);

    if (write(fd, buf, sizeof(buf)) == -1) {
        ErrHandling("write error");
    }
    close(fd);
    
    fd = open("./data.txt",  O_RDONLY);
    if (fd == -1) {
        ErrHandling("open() error");
    }

    char recv[kBufferSize];
    if (read(fd, recv, sizeof(recv)) == -1) {
        ErrHandling("read() error!");
    } 
    printf("recieve data: %s\n", recv);

    close(fd);
    return 0;
}

不能写完立即读,因为文件指针写完在末尾,直接读啥也没有

1.3 基于Windows平台的操作

1.3.1 准备工作

要在 Windows 上进行套接字编程,需要:

  1. 链接 ws2_32.lib 库。在 VS 中通过:项目–>属性–>配置属性–>链接器–>输入–>附加依赖项 添加 ws2_32.lib 库即可。
  2. 导入头文件 WinSock2.h。Windows 中有一个 winsock.h 和一个 WinSock2.h。其中 WinSock2.h 是较新版本,用来代替前者的。
  3. 实际上在 windows 上还需要通过:项目–>属性–>配置属性–>C++ 将 SDL 检查设为否,否则运行会出错。

1.3.2 Winsock的初始化

进行 Winsock 编程时,必须首先调用 WSAStartup 函数,设置程序中用到的 Winsock 版本,并初始化相应版本的库。

#include <WinSock2.h>

// wVersionRequested:要用的 Winsock版本信息,lpWSAData:WSADATA 结构体变量的地址值
// 成功时返回 0,失败时返回非 0 的错误代码值
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);  

有必要给出两个参数的详细说明

  1. WORD wVersionRequested:WORD 类型是通过 typedef 定义的 unsigned short 类型。Winsock 中存在多个套接字版本,要选择需要的版本,0x0102 表示 1.2 版本。
    可以用 MAKEWORD(2, 2) 来构造版本号,它构造了 2.2 版本的表示值,即返回 0x0202。
  2. LPWSADATA lpWSAData:LPWSADATA 是 WSADATA 类型的指针类型。没有特殊含义,只是为了调用函数,必须传递 WSADATA 类型变量的地址。

下面这段代码几乎是 Winsock 编程的公式。在进行 Winsock 编程时直接按下述方式编写即可。

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    ...
    if(WSAStartup(MAKEWORD9(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error");
    ...
    return 0;    
}

1.3.3 注销

#include <WinSock2.h>

// 调用此函数,Winsock 相关库将还给操作系统,无法再调用 Winsock 相关函数。
// 成功时返回 0,失败时返回 SOCKET_ERROR
int WSACleanup(void);  

1.4 Windows套接字相关函数和示例

#include <WinSock2.h>

// 成功时返回套接字句柄,失败时返回 INVALID_SOCKET
SOCKET socket(int af, int type, int protocol);      

 // 成功时返回 0,失败时返回 SOCKET_ERROR // 成功时返回 0,失败时返回 SOCKET_ERROR             
int bind(SOCKET s, const struct sockaddr* name, int namelen);   

// 成功时返回 0,失败时返回 SOCKET_ERROR
int listen(SOCKET s, int backlog);            

 // 成功时返回套接字句柄,失败时返回 INVALID_SOCKET                  
SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);   

// 成功时返回 0,失败时返回 SOCKET_ERROR
int connect(SOCKET s, const struct sockaddr* name, int namelen); 

// 成功时返回 0,失败时返回 SOCKET_ERROR
int closesocket(SOCKET s);                                       

Windows 中的句柄相当于 Linux 中的文件描述符,但是 Windows 中要区分文件句柄和套接字句柄,两者不完全一样。

Linux 中套接字也是文件,因此可以通过文件 I/O 函数 read 和 write 来进行数据传输。而 Windows 中严格区分文件 I/O 函数和套接字 I/O 函数。
Winsock 数据传输函数包括下面两个:

#include <WinSock2.h>

/** 
* s:套接字句柄;
* buf:待传输数据;
* len:要传输的字节数;
* flags:传输数据时用到的选项,一般可以写0。
*/
int send(SOCKET s, const char* buf, int len, int flags); 

/**
* s:套接字句柄
* len:可接收的最大字节数
* flags:接收数据时用到的选项,一般可以写 0。
* 成功时返回接收的字节数(遇到文件尾 EOF 时返回 0),失败时返回 SOCKET_ERROR
*/
int recv(SOCKET s, const char* buf, int len, int flags); 

demo

//hello_server.c
#pragma execution_character_set("utf-8")

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	SOCKADDR_IN servAddr, clntAddr;

	int szClntAddr;
	char message[] = "Hello World!";

	if (argc != 2)  // 检查参数数量
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)  // 初始化 Winsock 相关库
		ErrorHandling("WSAStartup() error!");

	hServSock = socket(PF_INET, SOCK_STREAM, 0);    // 创建套接字
	if (hServSock == INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;                  // 设置协议族
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);   // 设置 IP 地址
	servAddr.sin_port = htons(atoi(argv[1]));       // 设置端口号

	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)    // 为套接字分配地址和端口
		ErrorHandling("bind() error");

	if (listen(hServSock, 5) == SOCKET_ERROR)       // 使套接字转换为可接收连接的状态
		ErrorHandling("listen() error");

	szClntAddr = sizeof(clntAddr);
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);   // 接受连接请求,函数返回客户端的套接字
	if (hClntSock == INVALID_SOCKET)
		ErrorHandling("accept() error");

	send(hClntSock, message, sizeof(message), 0);   // 向客户端发送信息
	closesocket(hClntSock);     // 关闭服务器端套接字
	closesocket(hServSock);     // 关闭客户端套接字
	WSACleanup();       // 注销 Winsock 相关库
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

//hello_cilent.c
#pragma execution_character_set("utf-8")

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	char message[30];
	int strLen;

	if (argc != 3)
	{
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hSocket = socket(PF_INET, SOCK_STREAM, 0);
	if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.S_un.S_addr = inet_addr(argv[1]);   // 这里对书中代码进行了一些修改(源代码编译会报错,根据报错提示修改为当前代码)
	servAddr.sin_port = htons(atoi(argv[2]));

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() error!");

	strLen = recv(hSocket, message, sizeof(message) - 1, 0);
	if (strLen == -1)
		ErrorHandling("read() error!");
	printf("Message from server: %s \n", message);

	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值