《TCP/IP网络编程》第1,2章知识点汇总

课后习题答案可看:https://blog.csdn.net/qq_42603620/article/details/118605539

1. 开始网络编程

1.1 网络编程和套接字

网络编程就是编写程序使两台连网的计算机相互交换数据

套接字(socket):定义没找到合适的,暂时理解为就像一根电线,连接了两端。可以等学完之后再重新体会一下socket是什么。
socket为通信双方协商了采用什么协议族(如IPv4协议族),采用了什么样的传输方式(如面向连接的方式),和使用了什么协议,如TCP协议。

1.1.1 服务器端套接字

又称为监听套接字,有以下几个步骤

(1)调用socket函数创建套接字

#include <sys/socket. h>
int socket(int domain, int type, int protocol);
//成功时返回文件描述符,失败时返回-1

(2)调用bind函数分配地址信息

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen)
//成功时返回0 ,失败时返回-1

(3)调用listen函数将socket转为可接收状态

#include <sys/socket.h>
int listen(int sockfd, int backlog);
//成功时返回0,失败时返回-1

(4)调用accept接收客户端的连接请求

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//成功时返回文件描述符,失败时返回-1

1.1.2 客户端套接字

客户端流程相对更加简洁

(1)调用socket函数创建套接字

#include <sys/socket. h>
int socket(int domain, int type, int protocol);
//成功时返回文件描述符,失败时返回-1

(2)调用connect函数向服务器端请求连接

#include <sys/socket.h>
int connect(int sockfdJ struct sockaddr *serv_addr, socklen_t addrlen);
//成功时返回0, 失败时返回-1 

有bind和listen的是服务器端,没有的是客户端

1.1.3 “Hello World”服务端和客户端通信示例

服务器端 hello_server.c

目的:发送”Hello World“给客户端

#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)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

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 <port>\n", argv[0]);
        exit(1) ;
    }
	
    //1. 调用socket函数创建套接字。
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1)
        error_handling("socket() error");

    //2. 调用bind 函数分配IP地址和端口号。
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//htonl: 将主机数转换成无符号长整型的网络字节顺序(长)
    serv_addr.sin_port=htons(atoi(argv[1]));//atoi:字符串转整数;htons:将主机数转变成网络字节顺序(短)
    if(bind(serv_sock, (struct sockaddr* ) &serv_addr, sizeof(serv_addr))==-1)
        error_handling("bind() error");
    
    //3. 调用listen 函数将套接字转为可接收连接状态。
    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");
    
	clnt_addr_size=sizeof(clnt_addr);
    //4.调用accept函数受理连接请求。如果在没有连接请求的情况下调用该函数,则不会返回,直到有连接请求为止。
    clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    if(clnt_sock==-1)
		error_handling("accept() error");  
	
    //5. write函数用于传输数据,若程序经过 accept 代码执行到本行,则说明已经有了连接请求。
	write(clnt_sock, message, sizeof(message));
	close(clnt_sock);	
	close(serv_sock);
	return 0;
}
客户端 hello_client.c

目的:接收服务器端的“Hello World”

#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)
{
	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 <IP> <port>\n", argv[0]);
		exit(1);
	}
	
    //1. 创建套接字,但此时套接字并不马上分为服务器端和客户端。
    //如果紧接着调用bind、listen 函数,将成为服务器端套接字;如果调用connect函数,将成为客户端套接字。
	sock=socket(PF_INET, SOCK_STREAM, 0);
	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]));
	
    //2. 调用connect函数向服务器端发送连接请求。
	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) 
		error_handling("connect() error!");
	
    //3. read读数据,如果连接成功将会执行这一步
	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;
}

1.1.4 linux下的编译和运行

C语言编译器:GCC(GNU Compiler Collection,GNU编译器集合)

(1)编译和运行hello_server.c

gcc hello_server.c -o hserver //编译hello_server.c文件并生成可执行文件hserver
./hserver 9190 //运行当前目录下的hserver文件,需要传递端口号

-o 是用来指定可执行文件名的可选参数,如果省略则可执行文件默认为源文件名,即hello_server

(2)编译和运行hello_client.c

gcc hello_client.c -o hclient
./hclient 127.0.0.1 9190 //传递服务器端IP地址和端口号

./ 表示当前路径,可省略

(3)运行结果

一定要先运行服务器端,再运行客户端

在这里插入图片描述

客户端运行之后的结果

在这里插入图片描述

当服务器端和客户端在同一台机器上,127.0.0.1就代表本地计算机;如果不再同一台机器上,需要输入服务器端的ip地址

问题:
服务器端接受了一个客户端的连接并发送信息之后就自动关闭了,这显然不符合现实中要求,如何改进?

1.2 Linux平台

在Linux世界里, socket也被认为是文件的一种,因此在网络数据传输过程中自然可以使用文件I/O的相关函数。Windows则与Linux不同,是要区分socket文件的。因此在Windows中需要调用特殊的数据传输相关函数。

文件描述符:系统分配给文件或套接字,用于标识的整数
在Windows它被称为”句柄“,在Linux平台被称为”描述符“

在这里插入图片描述

1.2.1 打开文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *path , int flag);//path是路径,flag是打开模式
//成功时返回文件描述符,失败时返回-1

打开模式有:

在这里插入图片描述

1.2.2 关闭文件

#include <unistd.h>
int close(int fd);//fd是需要关闭的文件或套接字的文件描述符。
//成功时返回0,失败时返回-1。

在linux系统中,一切皆是文件,因此close不仅能关闭文件,还能关闭套接字

1.2.3 写入文件

#include <unistd.h>//unistd.h不是c语言的东西,是linux/unix的系统调用,包含了许多UNIX系统服务的函数原型
/*
fd: 显示数据传输对象的文件描述符。
buf: 保存要传输数据的缓冲地址值。
nbytes: 要传输数据的字节数。
*/
ssize_t write(int fd, const void * buf, size_t nbytes);
//成功时返回写入的字节数,失败时返回-1。

size_t是通过typedef声明的unsigned int类型
ssize_t是通过typedef声明的signed int类型。

ssize_t 、size_t等都是元数据类型(primitive),在sys/types.h头文件中一般由typedef声明定义

write示例

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>//放着O_CREAT等的定义

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

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;
}

在这里插入图片描述

1.2.4 读入文件

#include <unistd.h>
//fd: 显示数据接收对象的文件描述符
//buf: 要保存接收数据的缓冲地址值
//nbytes: 要接收数据的最大字节数
ssize_t read(int fd, void * buf, size_t nbytes);
//成功时返回接收的字节数(但遇到文件结尾则返回0),失败时返回-1

示例

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

#define BUF_SIZE 100

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

int main(void){
    int fd;
    char *buf[BUF_SIZE];

    fd = open("data.txt", O_RDONLY);
    if(fd == -1)
        error_handling("open() error!");
    printf("file descriptor: %d \n", fd);

    if(read(fd, buf, sizeof(buf)) == -1)
        error_handling("read() error!");
    printf("file data: %s", buf);
    close(fd);
    return 0;
}

在这里插入图片描述

1.2.5 比较文件和套接字的描述符

#include <stdio.h>
#include <fcntl.h>
#include <sys/socket.h>

int main(void)
{
    int fd1, fd2, fd3;

    fd1 = socket(PF_INET, SOCK_STREAM, 0);
    fd2 = open("data.txt", O_RDONLY);
    fd3 = socket(PF_INET, SOCK_STREAM, 0);
    //依次输出socket,文件,socket的描述符
    printf("file descriptors are: %d %d %d\n", fd1, fd2, fd3);
    //关闭文件
    close(fd1);close(fd2);close(fd2);
    return 0;
}

在这里插入图片描述

可以看到的是,在Linux中,socket和一般文件操作是一致的
而且描述符从3开始,是因为0,1,2分配给了标准I/0的描述符

1.3 Windows平台

大多数项目都在Linux系列的操作系统下开发服务器端,而多数客户端是在Windows平台下开发的。
Windows套接字大部分是参考BSD系列UNIX套接字设计的,和linux下编程很相似

1.3.1 测试代码

windows平台下将使用<winsock2.h>头文件。
但是注意到是,在windows下除了引入头文件之外,还需要链接库ws2_32.lib,否则无法运行。
如何链接库ws2_32.lib在下一小节中说明,这里可以先看看测试代码(会在后面介绍用法)

#include <stdio.h>
#include <winsock2.h>

int main(void)
{
    int fd1, fd2;

    WSADATA wsaData;
    fd1 = WSAStartup(MAKEWORD(2, 2), &wsaData);
    fd2 = WSAStartup(MAKEWORD(2, 2), &wsaData);
    printf("return values are: %d %d\n", fd1, fd2);//fd1和fd2都为0

    return 0;
}

1.3.2 使用配置

(1)如果是在Qt Creator(5.8)中

  • 引入 #include <winsock2.h>
  • 在工程文件(.pro)中,链接socket库ws2_32.lib,即添加:LIBS += -lpthread libwsock32 libws2_32

在这里插入图片描述

(2)如果是在visual studio(2010)中

  • 引入 #include <winsock2.h>
  • 同样需要链接库ws2_32.lib。方法一:可以通过在main函数中,添加 #pragma comment(lib,“ws2_32.lib”) 解决

在这里插入图片描述

  • 链接库的方法二:在工程属性的附加依赖项中添加ws2_32.lib。具体步骤是:项目–属性–链接器–输入-附加依赖性

在这里插入图片描述

(3)如果是在VS code中

  • windows下的vscode如果是第一次使用(比如#include报错),需要检查gcc是否安装好了。在命令行下输入 gcc -v
    (gcc安装)windows10下vscode配置C,C++环境:https://blog.csdn.net/widsoor/article/details/127116017
  • 引入 #include <winsock2.h>
  • 在自动生成的tasks.json文件中链接库 -lws2_32,位置一定要对(补充:这里更推荐使用winsock2.h对应的"lws2_32",”lwsock32“是winsock.h的对应命令)。这种方法应对的是非终端执行

在这里插入图片描述

  • 如果需要在vscode的终端用gcc运行,则需要在命令后面加上 -lws2_32,如
gcc hello_server_win.c -o hServerWin -lws2_32

1.3.3 Winsock介绍

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

#include <winsock2.h>
/*
wVersionRequested:程序员要用的Winsock版本信息。
IpWSAData: WSADATA结构体变量的地址值。
*/
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
//例如:WSAStartup(MAKEWORD(2, 2), &wsaData)
//成功时返回0 ,失败时返回非零的错误代码值。

(1)第1个参数

WORD是通过typedef定义的unsigned short类型,Winsock中存在多个版本,要准备WORD类型的套接字版本信息,传递给第一个参数
如版本为:1.2,那么主版本号为1,副版本号为2,传递的就是0X0201;高8位为副版本号,低8位为主版本号

MAKEWORD(1, 2); //主版本为1 ,副版本为2 ,返回0x0201

(2)第2个参数

LPWSADATA是WSADATA结构体的指针类型。调用完函数后,相应参数中将填充已初始化的库信息

(3)WSAStartup使用

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

(4)注销库

#include <winsock2.h>
int WSACleanup(void);//成功时返回0 ,失败时返回SOCKET ERROR

调用该函数时, Winsock相关库将归还Windows操作系统,无法再调用Winsock相关函数。

1.3.4 windows下套接字实例

相关函数

和linux相似,都是socket, bind, listen, accept, connect函数,一点区别是linux关闭套接字是用的close,windows使用closesocket

#include <winsock2.h>
//1. socket,成功时返回套接字句柄,失败时返回INVALID_SOCKET
SOCKET socket(int af, int type, int protocol);
//2. bind,成功时返回0,失败时返回SOCKET ERROR
int bind(SOCKET s, const struct sockaddr * name, int namelen);
//3. listen,成功时返回0,失败时返回SOCKET ERROR
int listen(SOCKET s, int backlog);
//4. accept,成功时返回套接字句柄,失败时返回INVALID_SOCKET
SOCKET accept(SOCKET s, struct sockaddr * addr, int * addrlen);
//5. connect,成功时返回0,失败时返回SOCKET_ERROR
int connect(SOCKET s, const struct sockaddr * name, int namelen);
//6. closesocket,成功时返回0,失败时返回SOCKET_ERROR
int closesocket(SOCKET s);

可以看到和linux下几乎没有区别,唯有2点,一个是socket和accept返回值是SOCKET,另一个是关闭套接字用closesocket()

linux下统一使用文件描述符,而windows区分文件句柄和套接字句柄,两者的相关函数有区别
Windows严格区分文件I/O 函数和套接字I/O函数,Winsock数据传输函数使用read和recv

#include <winsock2.h>
/*
1. send()函数
s: 表示数据传输对象连接的套接字句柄值
buf: 保存待传输数据的缓冲地址值
len: 要传输的字节数
flags: 传输数据时用到的多种选项信息。
*/
int send(SOCKET s, const char* buf, int len, int flags);
/*
2. recv()函数
s: 表示数据接收对象连接的套接字句柄值
buf: 保存接收数据的缓冲地址值
len: 能够接收的最大字节数
flags: 接收数据时用到的多种选项信息
*/
int recv(SOCKET s, const char *buf, int len, int flags);

linux也有send和recv;flags暂时写0,之后会讲解含义

服务器端 hello_server_win.c
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

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

int main(int argc, char* argv[])
{
    WSADATA wasData;
    SOCKET hServSocket, hClntSock;
    SOCKADDR_IN servAddr, clntAddr;
    int szClntAddr;
    char message[] = "Hello World!";

    if(argc!=2)//传入2个参数,第1个为函数本身,另1个为端口号
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    //1. WSAStartup: int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
    if(WSAStartup(MAKEWORD(2, 2), &wasData) != 0)
        ErrorHandling("WSAStartup() error!");
    
    //2. socket: SOCKET socket(int af, int type, int protocol)
    hServSocket = socket(PF_INET, SOCK_STREAM, 0);
    if(hServSocket == INVALID_SOCKET)
        ErrorHandling("socket() error!");
    
    //3. bind: int bind(SOCKET s, const struct sockaddr * name, int namelen);
    //在windows中,失败时用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]));
    if(bind(hServSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
        ErrorHandling("bind() error!");
    
    //4. listen: listen(SOCKET s, int backlog);
    if(listen(hServSocket, 5) ==  SOCKET_ERROR)
        ErrorHandling("listen() error!");
    
    //5. SOCKET accept(SOCKET s, struct sockaddr * addr, int * addrlen);
    szClntAddr = sizeof(clntAddr);
    hClntSock = accept(hServSocket, (SOCKADDR*)&clntAddr, &szClntAddr);
    if(hClntSock == INVALID_SOCKET)
        ErrorHandling("accept() error!");
    
    //6. send发送信息
    send(hClntSock, message, sizeof(message), 0);//在linux下用的是write,且只有前3个参数
    closesocket(hClntSock);
    closesocket(hServSocket);

    //6. 注销库
    WSACleanup();

    return 0;
}

windows下各个函数用0表示正常返回,SOCKET_ERROR表示错误返回;而linux下是用-1表示错误返回

客户端 hello_client_win.c
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

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

int main(int argc, char* argv[]){
    WSADATA wasData;
    SOCKET hSocket;
    SOCKADDR_IN servAddr;
    char message[30];
    int strlen;

    if(argc!=3)//传入3个参数,第1个为函数本身,第2个为服务端地址,第3个为端口号
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    //1. WSAStartup
    if(WSAStartup(MAKEWORD(2, 2), &wasData) != 0)
        ErrorHandling("WSAStartup() error!");
    
    //2. socket
    hSocket = socket(PF_INET, SOCK_STREAM, 0);
    if(hSocket == INVALID_SOCKET)
        ErrorHandling("socket() error!");
    
    //3. connect
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));
    if(connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == INVALID_SOCKET)
        ErrorHandling("connect() error!");
    
    //4. 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();

    return 0;
}
编译和运行

(这里用了lwsock32,但更应该使用 lws2_32)
服务器端

gcc hello_server_win.c -o hServerWin -lwsock32
./hServerWin 9190

客户端

gcc hello_client_win.c -o hClientWin -lwsock32
./hClientWin 127.0.0.1 9190

可以看到客户端接收到发来的信息

在这里插入图片描述

思考:
为什么客户端不写 bind() ?

1.3.5 练习

参考示例 low_open.c 和 low_read.c,分别利用底层文件I/O和ANSI标准IO编写文件复制程序

(1)底层I/O

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#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);
	dst=open("dst.dat", O_CREAT|O_WRONLY|O_TRUNC);
	if(src==-1||dst==-1)
	{
		puts("file open error");
		return -1;
	}
	//每次读取100个字符
	while((read_cnt=read(src, buf, BUF_SIZE))!=0)
		write(dst, buf, read_cnt);

	close(src);
	close(dst);
	return 0;
}

(2)ANSI标准I/O

#include <stdio.h>
#define BUF_SIZE  3

int main(void)
{
	char buf[BUF_SIZE];
	int readCnt;

	FILE * src=fopen("src.dat", "rb");
	FILE * des=fopen("dst.dat", "wb");
	
	if(src==NULL || des==NULL)
	{
		puts("file open error");
		return -1;
	}
	
    /*
    size_t fread(void* buffer, size_t size, size_t count, FILE*stream);
    fread函数每次从stream中最多读取count个单元,每个单元大小为size个字节,将读取的数据放到buffer;
    文件流的指针后移size*count字节。
    */
	while((readCnt=fread((void*)buf, 1, BUF_SIZE, src))!=0)//每次读取3个字符
		fwrite((void*)buf, 1, readCnt, des);

	fclose(src);
	fclose(des);
	return 0;
}

2. 套接字类型与协议设置

2.1 socket

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

socket 3个参数的含义分别是:
domain: 套接字中使用的协议族(Protocol Family)信息
type: 套接字数据传输类型信息。
protocol: 计算机间通信中使用的协议信息

1. 协议族(Protocol Family)

在这里插入图片描述

最重要的是代表IPv4互联网协议族的PF_INET
套接字中实际采用的最终协议信息是通过socket函数的第三个参数传递的。在指定的协议族范围内通过第一个参数决定第三个参数。

2. 套接字类型

  • 面向连接的套接字(SOCK_STREAM)(TCP协议)

“传输数据的计算机通过3次调用write函数传递了100字节的数据,但接收数据的计算机仅通过1次read函数调用就接收了全部100个字节。”

数据不丢失、按序传输数据、传输的数据不存在数据边界(write和read的调用次数并无太大意义)
套接字内部有一个缓冲(buffer),实质是一个字节数组
可靠传输:当缓冲区满时,停止写入;如果传输出错,提供重传服务

套接字连接必须一一对应。面向连接的套接字只能与另外一个同样特性的套接字连接

int tcp_socket = socket(PF_INET, SOCK_STREAM, 0);
  • 面向消息的套接字(SOCK_DGRAM )(UDP协议)

快速传输、传输数据可能损坏或丢失、存在数据边界(接收次数和传输次数应当一致)、限制每次传输的大小
面向消息的套接字不存在连接的概念

int udp_socket = socket(PF_INET, SOCK_DGRAM, 0);

3. 协议的最终选择

大部分情况下,前2个参数足以决定套接字采取的协议,这个时候,第3个参数没有发挥作用,设置为0
当“同一协议族中存在多个数据传输方式相同的协议”,即协议族和数据传输方式都相同,但协议不同。此时需要第3个参数指定具体协议信息

例如:采用IPv4网络协议族,并且使用SOCK_STREAM面向连接的传输方式的协议只有一个,就是 IPPROTO_TCP,被称为TCP套接字

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);//这和第3个参数为0是一样的

例如:采用IPv4网络协议族,并且使用SOCK_DGRAM面向消息的传输方式的协议只有一个,就是 IPPROTO_UDP,被称为UDP套接字

int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);//这和第3个参数为0是一样的

TCP套接字示例:一次write,多次read

沿用第一章的”Hello World“服务端和客户端通信示例,hello_server.c不作改动,hello_client.c修改如下

/*
str_len=read(sock, message, sizeof(message)-1);
if(str_len==-1)
	error_handling("read() error!");
*/
int read_len = 0, idx = 0;
str_len = 0;
while(read_len = read(sock, &message[idx++], 1))//每次仅接收1个字节
{
    if(read_len == -1)
        error_handling("read() error!");
    str_len += read_len;
}
printf("Function read call count: %d \n", str_len);

在这里插入图片描述
最终可以看到,服务器端一次性write了所有数据,而客户端却read了13次,这是TCP所允许的

TCP套接字示例:多次write,一次read

沿用第一章的”Hello World“服务端和客户端通信示例,hello_client.c不作改动,hello_server.c修改如下

/*
write(clnt_sock, message, sizeof(message));
*/
int strlen = sizeof(message);
int write_cnt = 0;
while(write_cnt < strlen)
{
    write(clnt_sock, message+write_cnt, 1);//每次只写入1个字节
    write_cnt++;
}
printf("Function write call count: %d \n", strlen);

在这里插入图片描述
这里虽然顺利通过,但有的时候为了匹配服务器端写入数据的速度,客户端要在read前加入一段无意义的延迟代码,如

for(i=0; i<3000; i++)		 //busy waiting!!
	printf("Wait time %d \n", i);

让CPU执行多余任务以延迟代码运行的方式称为”Busy Waiting“,使用得当即可推迟函数调用。

2.2 windows平台

#include <WinSock2.h>
SOCKET socket(int af, int type, int protocol);

windows下socket的3个参数含义和linux平台是一样的,只是返回值类型有差异

SOCKET是一个结构体,用来保存整数型套接字句柄值。实际上,socket返回的也可以用int变量接收,但为了扩展性,使用了结构体,表示专门用于接收套接字句柄

创建套接字失败时返回 INVALID_SOCKET,这其实是一个常数 -1

TCP套接字示例

相同的修改,服务器端不变,客户端多次read

/*
strlen = recv(hSocket, message, sizeof(message)-1, 0);
if(strlen == -1)
	ErrorHandling("read() error!");
*/
int readlen = 0, idx = 0;
strlen = 0;
while(readlen = recv(hSocket, &message[idx++], 1, 0))
{
    if(readlen == -1)
        ErrorHandling("read() error!");
    strlen += readlen;
}
printf("Function read call count: %d \n", strlen);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值