第一章 网络编程和套接字

HelloWord!

接受连接请求的套接字创建过程

  1. 调用socket函数创建套接字
  2. 调用bind函数分配IP地址和端口号
  3. 调用listen函数转为可接收请求状态
  4. 调用accept函数受理连接请求

hello_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);
int main(int argc, char* argv){
	int serv_sock;
	int clnt_sock;
	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	scoklen_t clnt_addr_size;
	
	char message[] = "Hello World!";
	
	if(argc!=2){
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);      // 1. 调用socket函数创建套接字
	if(serv_sock == -1)
		error_handling("socket() error");
	
	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]));
	if(bind(serv_sock, (struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)     // 2. 调用bind函数分配IP地址和端口号                          
		error_handing("bind() error");
	if(listen(serv_sock, 5)==-1)    // 3. 调用listen函数将套接字转化为可接收连接状态
		error_handling("listen() error");
	
	clnt_adder_size=sizeof(clnt_addr);
	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);     // 4. 调用accept函数受理连接请求。如果没有连接请求的情况下调用该函数,则不会返回,直到有连接请求为止
	if(clnt_sock==-1)
		error_handling("accept() error");
	
	write(clnt_sock, message, sizeof(message));     // 5. write函数用于传输数据,若程序经过第4布执行到本行,说明已经有了连接请求
	close(clnt_sock);
	close(serv_sock);
	return 0;
}
void error_handling(char *message){
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

hello_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);
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 <IP> <port>\n", argv[0]);
		exit(1);
	}
	sock=socket(PF_INET, SOCK_STREAM, 0);       // 1. 创建套接字,但此时套接字并不马上分为服务器端和客户端。如果紧接着调用bind、listen函数,将成为服务器端套接字; 如果调用connect函数,将成为客户端套接字
	if(sock==-1)
		error_handling("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)      // 2. 调用connect函数向服务器端发送连接请求
		error_handling("connect() error!");
	
	str_len=read(sock, message, sizeof(message)-1);
	if(str_len==-1)
		error_handling("read() error!");
	printf("Message from servere : %s \n", message);
	close(sock);
	return 0;
}
void error_handling(char *message){
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

运行

gcc hello_server.c -o hserver 生成可执行文件hserver
./hserver 9190 运行当前目录下hserver文件
gcc hello_client.c -o hclient
./hclient 127.0.0.1 9190
Message from server: Hello World!

消息完成完后,服务器端和客户端都停止运行

再次运行程序前需等待
上面服务端无法立即重新运行。如果再次运行,需更改端口号。后面解释原因

基于Linux的文件操作

Linux中,socket也是一种文件,网络数据传输中可使用I/O相关函数
Windows和Linux不同,要区分socket和文件的,Windows需要调用特殊的数据传输相关函数

底层文件访问(Low-Lever File Access)和文件描述符(File Descriptor)

  • 文件描述符是系统分配给文件或套接字的整数

分配给标准输入及标准错误的文件描述符:
在这里插入图片描述
文件和套接字要经过创建才被分配文件描述符,而上面三种无需创建程序开始运行就自动分配文件描述符

  • Windows中叫“句柄”,Linux中叫“描述符”
  • 文件打开、写入、读取、关闭

打开文件

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

int open(const char *path, int flag);
成功返回文件描述符,失败返回-1

path: 文件名的字符串地址
flag:  文件打开模式信息

文件打开模式:
在这里插入图片描述
关闭文件

#include <unistd.h>

int close(int fd);
成功返回0, 失败返回-1
fd:需要关闭的文件或套字的文件描述符

将数据写入文件

#include <unistd.h>

ssize_t write(int fd, const void * buf, size_t nbytes);
成功返回写入的字节数,失败返回-1

fd:显示数据传输对象的文件描述符
buf:保存要传输数据的缓存地址值
nbytes:要传输数据的字节数

size_t通过typedef声明的unsigned int类型
ssize_t:size_t前面多加一个s表示是signed
ssize_t是通过typedef声明的signed int类型

元数据类型(primitive)
_t为后缀的数据类型,sys/types.h头文件中由typedef定义
不同操作系统int的位数不同,有32或16, 用size_t或ssize_t生命为四个字节的,后续代码如果修改就很方便,只需修改并编译size_t和typedef声明即可。
一般使用typedef和_t后缀声明基本数据类型的别名

例子:创建新文件并保存数据

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unsistd.h>
void error_handling(char* message)
int main(void){
	int fd;
	char buf[] = "Let's go!\n";
	fd = open("data.txt", O_CREAT|O_WRONLY|O_TRUNC);    // 1. 打开模式为这三个,将创建空文件,只能写。若存在data.txt,清空文件全部数据
	if(fd == -1)
		error_handling("open() error!");                // 2. 打开失败	
	printf("file descriptor: %d \n", fd);
	if(write(fd, buf, sizeof(buf))==-1)                 // 3. 写入buf字符串到文件
		error_handling("write() error!");               // 4. 写入文件失败处理
	close(fd);                                          // 5. 关闭文件
	return 0;
}
void error_handling(char *message){
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}


运行

gcc low_open.c -o lopen
./lopen
file descriptor: 3
cat data.txt
Let's go~

读取文件

#include <unistd.h>
ssize_t read(int fd, void * buf, size_t nbytes);
成功返回接收的字节数(遇到文件结尾返回0),失败返回-1

fd:显示数据接收对象的文件描述符
buf:要保存接收数据的缓存地址值
nbytes:要接收数据的最大字节数

***例子通过read函数读取data.txt保存的数据

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100
void error_handling(char* message);
int main(void){
	int fd;
	char buf[BUF_SIZE];
	
	fd=open("data.txt", O_RONLY);               // 1. 打开读取专用文件data.txt
	if(fd==-1)
		error_handling("open() error!");
	printf("file descriptor: %d \n", fd);
	if(read(fd, buf, sizeof(buf)) == -1)        // 2. 调用read向buf中保存读入的数据
		error_handling("read() error!");
	printf("file data: %s, buf");
	close(fd);	
	return 0;
}
void error_handling(char *message){
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

运行

gcc low_read.c -o lread
./lread
file descriptor: 3
file data: Let's go!

文件描述符与套接字

fd_seri.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
int main(void){
	int fd1, fd2, fd3;
	fd1 = socket(PF_INET, SOCK_STREAM, 0);                  // 1. 创建1个文件和2个套接字
	fd2 = open("test.dat", O_CREAT|O_WRONLY|O_TRUNC);
	fd3 = socket(PF_INET, SOCK_DGRAM, 0);
	
	printf("file descriptor 1: %d\n", fd1);                 // 输出创建的文件描述符的整数值
	printf("file descriptor 2: %d\n", fd2);
	printf("file descriptor 3: %d\n", fd3);
	close(fd1); close(fd2); close(fd3);
	return 0;
}

运行

gcc fd_seri.c -o fds
./fds
file descriptor 1: 3
file descriptor 2: 4
file descriptor 3: 5

0、 1、 2是分配给I/O的描述符,输出结果描述符是从小到大编号

windows系统实现

  • Windows套接字头文件和库
  1. 导入头文件winsock2.h
  2. 链接ws2_32.lib库
    VisualStudio 项目属性-链接器-输入-附加依赖项:
    在这里插入图片描述
    添加ws2_32.lib:
    在这里插入图片描述
  • Winsock初始化
#include <winsock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
成功返回0,失败返回非零的错误代码值
wVersionRequested:程序员要用的Winsock版本信息
lpWSAData:WSADATA结构体u变量的地址值

Winsock版本信息
WORD类型(typedef声明定义的unsigned short类型)套接字版本信息

wVersionRequested: 若版本号为1.2,则1是主版本号,2是副版本号,传递0x0201

高8位为副版本号,低8位为主版本号

本书使用2.2.版本,传递0x0202

借助MAKEWORD宏函数构建WORD型版本信息:
MAKEWORD(1, 2);: // 主版本为1,副版本为2,返回0x0201
MAKEWORD(2, 2);: // 主版本为2,副版本为2,返回0x0202

lpWSADATA(WSADATA型结构体变量地址)

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

调用完函数后,相应参数中填充已初始化的库信息

注销该库

#include <winsock2.h>

int WSACleanup(void);
成功返回0, 失败返回SOCKET_ERROR

调用该函数,Winsock相关库归还Windows操作系统,无法再调用Winsock相关函数。无需再使用Winsock函数时才调用,通常在程序结束之前调用

基于Windows套接字相关函数和实例

例子

hello_server_win.c

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
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) // 初始化套接字库
		ErrorHandling("WSAStartup() error!");
	hServSock = socket(PF_INET, SOCK_STREAM, 0); // 成功时返回套接字句柄,失败时返回INVALID_SOCKET
	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);
	servAddr.sin_port = htons(atoi(argv[1]));
	// 调用bind函数给套接字分配IP地址和端口号,成功时返回0,失败时返回SOCKET_ERROR
	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("bind() error");
	// 调用listen,使hServSock成为服务端套接字,成功时返回0,失败时返回SOCKET_ERROR
	if (listen(hServSock, 5) == SOCKET_ERROR)
		ErrorHandling("listen() error");
	szClntAddr = sizeof(clntAddr);
	// 调用accept函数受理客户端连接请求,成功时返回套接字句柄,失败时返回INVALID_SOCKET
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
	if (hClntSock == INVALID_SOCKET)
		ErrorHandling("accept() error");
	// 调用send向客户端发送数据
	send(hClntSock, message, sizeof(message), 0);
	closesocket(hClntSock); // 关闭套接字,成功时返回0,失败时返回SOCKET_ERROR
	closesocket(hServSock);
	WSACleanup(); // 程序终止前注销第20行中初始化的套接字库
	return 0;
}
void ErrorHandling(char* message) {
	fputs(message, stderr);
	fputs((char*)'\n', stderr);
	exit(1);
}

hello_client_win.c

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
void ErrorHandling(const 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);
	}
	// 初始化Winsock库
	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_addr = inet_pton(argv[1]); // 老版本函数
	inet_pton(AF_INET, argv[1], (void*)&servAddr.sin_addr.s_addr);
	servAddr.sin_port = htons(atoi(argv[2]));
	// 调用connect通过hSocket套接字向服务器发送连接请求,成功时返回0,失败时返回SOCKET_ERROR
	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() error!");
	// 调用recv函数接收服务器发来的数据
	strLen = recv(hSocket, message, sizeof(message) - 1, 0);
	if (strLen == 1)
		ErrorHandling("read() error!");
	printf("Message from server: %s \n", message);
	closesocket(hSocket);
	WSACleanup(); // 注销已初始化的Winsock库
	return 0;
}
void ErrorHandling(const char* message) {
	fputs(message, stderr);
	fputs((char*)'\n', stderr);
	exit(1);
}

运行

#启动服务端
c:\tcpip>hClientWin 127.0.0.1 9190   	# hServerWin是生成的exe文件
Message from server: Hello World!

#启动客户端
c:\tcpip>hClientWin 127.0.0.1 9190
Message from server: Hello World!

Windows中文件句柄和套接字句柄

Linux内部也将套接字当作文件,创建文件和套接字都返回文件描述符
Windows通过调用系统函数创建文件,返回句柄(类似Linux 文件描述符),但Window区分文件句柄和套接字句柄

IO函数

  • Linux IO函数
    套接字也是文件,可以使用read和write函数读写
write(SOCKET s, const char* buf, int len)

Linux也有send和recv方法,也来自BSD套接字,作用和Windows的一样

  • Windows IO函数
    严格区分文件IO和套接字IO
    send函数
send(SOCKET s, const char* buf, int len, int flags),与Linuxwrite比多一个flags参数,为0表示不设置任何选项
成功返回传输字节数,失败返回SOCKET_ERROR

s:表示数据传输对象连接的套接字句柄值
buf:保持待传输数据的缓冲地址值
len:要传输的字节数
flags:传输数据时用到的多种选项信息

send函数非windows独有,linux也有,也来自BSD套接字

recv函数

#include <winsock2.h>
int recv(SOCKET s, const char *buf, int len, int flags);
成功返回接收的字节数(收到EOF时返回0),失败返回SOCKET_ERROR
s:表示数据接收对象连接的套接字句柄值
buf:保存接收数据的缓冲地址值
len:能够接收的最大字节数
flags:接收数据时用到的多种选项信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值