TCP/IP网络编程C01-理解网络编程和套接字

学习笔记

网络编程(套接字编程)就是编写程序使两台联网的计算机互相交换数据,而套接字就是用来连接网络的工具。

服务器端套接字

#include 
int socket(int domain, int type, int protocol);       
// 功能:创建套接字。
// 参数:domain:采取的协议族,一般为 PF_INET;type:数据传输方式,一般为 SOCK_STREAM;protocol:一般设为 0 即可。
// 返回值:成功时返回文件描述符,失败时返回 -1
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);  
// 功能:为套接字分配地址信息。
// 参数:sockfd:要分配地址信息的套接字文件描述符;myaddr:存有地址信息的结构体变量指针;addrlen:第二个参数的长度。
// 返回值:成功时返回 0,失败时返回 -1
int listen(int sockfd, int backlog);  
// 功能:将套接字转换为可接收连接的状态。
// 参数:sock:希望进入等待连接请求状态的套接字文件描述符;backlog:连接请求等待队列的长度,最多使 backlog 个连接请求进入队列。
// 返回值:成功时返回 0,失败时返回 -1
int accept(int sockfd, struct sockaddr *addr, socklen_t addrlen);  
// 功能:受理连接请求等待队列中待处理的连接请求。
// 参数:sock:服务器套接字的文件描述符;addr:用于保存发起连接请求的客户端地址信息;addrlen:第二个参数的长度。
// 返回值:成功时返回创建的套接字文件描述符,失败时返回 -1

接受连接请求的服务器端套接字编程流程:

  1. 调用 socket 函数创建套接字;

  2. 调用 bind 函数为套接字分配 IP 地址与端口号;

  3. 调用 listen 函数将套接字转换为可接收状态;

  4. 调用 accept 函数受理连接请求。accept 会阻塞,直到有连接请求才会返回;

  5. 调用 read/write 函数进行数据交换;

  6. 调用 close 函数断开连接;

客户端套接字

#include 
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);  
// 功能:请求连接。
// 参数:sock:客户端套接字的文件描述符;serv_addr:保存目标服务器端地址信息的结构体指针;addrlen:第二个参数的长度(单位是字节)
// 返回值:成功时返回 0,失败时返回 -1

客户端请求连接步骤:

  1. 调用 socket 函数创建套接字;

  2. 调用 connect 函数请求连接;

  3. 调用 read/write 函数进行数据交换;

  4. 调用 close 函数断开连接;

客户端的 IP 地址和端口在调用 connect 函数时自动分配,无需调用 bind 函数。

基于Linux的文件操作

//file descriptor

#include       // fcntl.h 和 unistd.h 包含的内容有些相似,包括 open 函数等。总之使用文件函数时将 fcntl.h 和 unistd.h 都 include 就可以了
#include
int open(const char *path, int flag);                   
// 功能:按 flag 指定的模式打开文件。
// 参数:path:文件名的地址;flag:文件打开的模式。
// 返回值:成功时返回文件描述符,失败时返回 -1
int close(int fd);
// 功能:关闭 fd 对应的文件或套接字。当关闭一个套接字时会向对方发送 EOF。
// 参数:fd:文件或套接字的文件描述符。
// 返回值:成功时返回 0,失败时返回 -1
ssize_t read(int fd, void* buf, size_t nbytes);  
// 功能:从文件 fd 读取数据。read 函数会阻塞,直到读取到数据或 EOF 才返回。
// 参数:fd:文件描述符;buf:保存要接收的数据;nbytes:要接收的最大字节数。
// 返回值:成功时返回接收的字节数(遇到文件尾则返回 0),失败时返回 -1
ssize_t write(int fd, const void* buf, size_t nbytes);  
// 功能:向文件 fd 输出数据。
// 参数:fd:文件描述符;buf:要传输的数据;nbytes:要传输的字节数。
// 返回值:成功时返回写入的字节数,失败时返回 -1

EOF 即表示文件尾。

size_t 的类型是 unsigned int,ssize_t 的类型是 signed int。

文件描述符 (文件句柄)

  • Linux 中套接字描述符也是文件,因此通过套接字发送、接收数据就和读写文件一样,通过 read、write 这些函数来接收、发送数据。
  • 文件描述符是系统分配给文件或套接字的整数。
  • 0、1、2 分别由系统分配给了标准输入、标准输出和标准错误。
  • 文件和套接字创建时才会被分配文件描述符。它们的文件描述符会从 3 开始按序递增。
  • Windows 系统中术语”句柄“和 Linux 中的文件描述符含义相同。
文件描述符 对象
0 标准输入:Standard Input
1 标准输出:Standard Output
2 标准错误:Standard Error

文件打开模式(flag)

用位或运算|组合多个模式

打开模式 含义
O_CREAT 必要时创建文件
O_TRUNC 删除全部现有数据
O_APPEND 追加到已有数据后面
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开

习题答案

Q01

套接字在网络编程中的作用是什么?为什么称它为套接字?

P2,网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下,我们只需要考虑如何编写传输软件。操作系统提供了名为 “套接字”,套接字是网络传输用的软件设备
socket 英文原意是插座:我们把插头插到插座上就能从电网获得电力供给,同样,为了与远程计算机进行数据传输,需要连接到 Internet, 而变成中的 “套接字” 就是用来连接该网络的工具

Q02

在服务器端创建套接字后,会依次调用 listen 函数和 accept 函数。请比较并说明两者作用

listen: 调用 listen 函数将套接字转换成可受连接状态(监听)
accept: 调用 accept 函数受理连接请求,并且在没有连接请求的情况调用该函数,不会返回。直到有连接请求为止。二者存在逻辑上的先后关系

Q03

Linux 中,对套接字数据进行 I/O 时可以直接使用 I/O 相关函数;而在 Windows 中则不可以。原因为何?

Linux 把套接字也看作是文件,所以可以用文件 I/O 相关函数;而 Windows 要区分套接字和文件,所以设置了特殊的函数

Q04

创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?

套接字被创建之后,只有为其分配了IP地址和端口号后,客户端才能够通过IP地址及端口号与服务器端建立连接,需要调用 bind 函数来完成地址分配。分配地址是通过bind()函数实现

Q05

Linux 中的文件描述符与 Windows 的句柄实际上非常类似。请以套接字为对象说明他们的含义。

Linux 的文件描述符是为了区分指定文件而赋予文件的整数值(相当于编号)。Windows 的文件描述符其实也是套接字的整数值,其目的也是区分指定套接字。

Q06

底层文件 I/O 函数与 ANSI 标准定义的文件 I/O 函数之间有何区别?

在ANSI标准中定义的I/O函数是作为C的标准提供的函数,无论操作系统如何,随时都可以调用。另一方面,低级文件I/O函数是操作系统提供的I/O函数。

Q07

参考本书给出的示例 low_open.c 和 low_read.c, 分别利用底层文件 I/O 和 ANSI 标准 I/O 编写文件复制程序。可任意指定复制程序的使用方法。

   /*****************************low_cpy.c(底层文件I/O)*********************************/
   #include 
   #include 
   #include 
   #define BUF_SIZE 100
   
   int main(int argc, char *argv[]) 
   {
   	int src, dst;
   	int read_cnt;
   	char buf[BUF_SIZE];
   
   	src=open("src.dat", O_RDONLY);//通过调用open来打开文件
   	dst=open("dst.dat", O_CREAT|O_WRONLY|O_TRUNC);
   	if(src==-1||dst==-1)
   	{
   		puts("file open error");
   		return -1;
   	}
   
   	while((read_cnt=read(src, buf, BUF_SIZE))!=0)//通过调用read来读取文件
   		write(dst, buf, read_cnt);//通过调用write来写文件
   
   	close(src);
   	close(dst);
   	return 0;
   }
   
   /*****************************ansi_cpy(ANSI标准I/O )*********************************/
   #include 
   #define BUF_SIZE  30
   
   int main(void)
   {
   	char buf[BUF_SIZE];
   	int readCnt;
   
   	FILE * src=fopen("src.dat", "rb");//通过调用fopen来打开文件
   	FILE * des=fopen("dst.dat", "wb");
   
   	if(src==NULL || des==NULL)
   	{
   		puts("file open error");
   		return -1;
   	}
   
   	while(1)
   	{
   		readCnt=fread((void*)buf, 1, BUF_SIZE, src);//通过调用fread来读取文件
   
   		if(readCnt<BUF_SIZE)
   		{
   			if(feof(src)!=0)
   			{
   				fwrite((void*)buf, 1, readCnt, des);//通过调用fwrite来写文件
   				break;
   			}
   			else
   				puts("file cpy error()");
   
   			break;
   		}
   		fwrite((void*)buf, 1, BUF_SIZE, des);
   	}
   
   	fclose(src);
   	fclose(des);
   	return 0;
   }

书本源码

01-hello_server.c

#include 
#include 
#include 
#include 
#include 
#include 


/*
struct sockaddr_in
{
    sa_family       sin_family;     //地址族(AF_INET|AF_INET6|...),两个字节
    uint16_t        sin_port;       //16位端口号
    struct in_addr  sin_addr;       // 表示 32 位 IP 地址的结构体
    char        sin_zero[8];        //占位用(必须填充为0)
}

struct in_addr
{
    In_addr_t   s_addr;              // 32 位 IP 地址,实际位为 uint32_t 类型
}
*/


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;
	socklen_t clnt_addr_size;

	char message[]="Hello World!";
	
	if(argc!=2){
		printf("Usage : %s \n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);//第一步socket
	if(serv_sock == -1)
		error_handling("socket() error");
	
	//填写服务器端的IP地址和端口
	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 )//第二步bind
		error_handling("bind() error"); 
	
	if(listen(serv_sock, 5)==-1)//第三步listen
		error_handling("listen() error");
	
	clnt_addr_size=sizeof(clnt_addr);  
	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);//第四步accept
	if(clnt_sock==-1)
		error_handling("accept() error");  
	
	write(clnt_sock, message, sizeof(message));//发送消息
	close(clnt_sock);	
	close(serv_sock);
	return 0;
}

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


/************** 输入******************






/******************** input******************
description:
建立tcp服务器端。输入需要指定端口号

content:
./01-hello_server  9190

*******************************************/



/******************** output******************
description:
无输出

content:

*******************************************/


02-hello_client.c

#include 
#include 
#include 
#include 
#include 
#include 

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  \n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);//第一步socket
	if(sock == -1)
		error_handling("socket() error");
	
	//填写目的端的IP地址和端口
	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) //第二步connect
		error_handling("connect() error!");
	
	str_len=read(sock, message, sizeof(message)-1);
	if(str_len==-1)
		error_handling("read() error!");
	
	printf("Message from server: %s \n", message);  
	close(sock);
	return 0;
}

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



/******************** input******************
description:
建立tcp客户端,需要制定ip和端口号


content:

 ./02-hello_client  127.0.0.1 9190

*******************************************/



/******************** output******************
description:

接收来自服务器端的消息

content:
Message from server: Hello World! 

*******************************************/

03-low_open.c


#include 
#include 
#include 
#include 

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);//用或运算追加条件
	if(fd==-1)
		error_handling("open() error!");
	printf("file descriptor: %d \n", fd);

	if(write(fd, buf, sizeof(buf))==-1)//将内容写进文件里
		error_handling("write() error!");

	close(fd);
	return 0;
}

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

/******************** input******************
description:



content:

cat data.txt

*******************************************/



/******************** output******************
description:



content:
file descriptor: 3

Let's go!
*******************************************/

04-low_read.c

#include 
#include 
#include 
#include 

#define BUF_SIZE 100

void error_handling(char* message);

int main(void)
{
	int fd;
	char buf[BUF_SIZE];
	
	fd=open("data.txt", O_RDONLY);//open
	if( fd==-1)
		error_handling("open() error!");
	
	printf("file descriptor: %d \n" , fd);
	
	if(read(fd, buf, sizeof(buf))==-1)//read
		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);
}

/******************** input******************
description:



content:



*******************************************/



/******************** output******************
description:



content:
file descriptor: 3 
file data: Let's go!

*******************************************/

05-fd_seri.c

#include 
#include 
#include 
#include 

int main(void)
{	
	int fd1, fd2, fd3;
	fd1=socket(PF_INET, SOCK_STREAM, 0);
	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;
}


/******************** input******************
description:



content:



*******************************************/



/******************** output******************
description:

描述符从3开始以由小到大的顺序编号( numbering),0,1,2已经被占用

content:
file descriptor 1: 3
file descriptor 2: 4
file descriptor 3: 5

*******************************************/

参考链接

  • 《TCP/IP网络编程》课后练习答案第一部分1~5章 尹圣雨_KongJHong的博客-CSDN博客_tcpip网络编程课后答案
  • phoon/TCP-IP-NP: 《TCP/IP网络编程》((韩)尹圣雨) 学习笔记
  • 《TCP/IP网络编程》学习笔记整理 - 从园客博开始 - 博客园
  • riba2534/TCP-IP-NetworkNote: 《TCP/IP网络编程》(韩-尹圣雨)学习笔记
  • hclg/tcp_ip: TCP/IP 网络编程

你可能感兴趣的:(网络,tcp/ip,网络协议)