网络编程(套接字编程)就是编写程序使两台联网的计算机互相交换数据,而套接字就是用来连接网络的工具。
#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
接受连接请求的服务器端套接字编程流程:
调用 socket 函数创建套接字;
调用 bind 函数为套接字分配 IP 地址与端口号;
调用 listen 函数将套接字转换为可接收状态;
调用 accept 函数受理连接请求。accept 会阻塞,直到有连接请求才会返回;
调用 read/write 函数进行数据交换;
调用 close 函数断开连接;
#include
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
// 功能:请求连接。
// 参数:sock:客户端套接字的文件描述符;serv_addr:保存目标服务器端地址信息的结构体指针;addrlen:第二个参数的长度(单位是字节)
// 返回值:成功时返回 0,失败时返回 -1
客户端请求连接步骤:
调用 socket 函数创建套接字;
调用 connect 函数请求连接;
调用 read/write 函数进行数据交换;
调用 close 函数断开连接;
客户端的 IP 地址和端口在调用 connect 函数时自动分配,无需调用 bind 函数。
//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。
文件描述符 | 对象 |
---|---|
0 | 标准输入:Standard Input |
1 | 标准输出:Standard Output |
2 | 标准错误:Standard Error |
用位或运算|
组合多个模式
打开模式 | 含义 |
---|---|
O_CREAT | 必要时创建文件 |
O_TRUNC | 删除全部现有数据 |
O_APPEND | 追加到已有数据后面 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
套接字在网络编程中的作用是什么?为什么称它为套接字?
P2,网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下,我们只需要考虑如何编写传输软件。操作系统提供了名为 “套接字”,套接字是网络传输用的软件设备
socket 英文原意是插座:我们把插头插到插座上就能从电网获得电力供给,同样,为了与远程计算机进行数据传输,需要连接到 Internet, 而变成中的 “套接字” 就是用来连接该网络的工具
在服务器端创建套接字后,会依次调用 listen 函数和 accept 函数。请比较并说明两者作用
listen: 调用 listen 函数将套接字转换成可受连接状态(监听)
accept: 调用 accept 函数受理连接请求,并且在没有连接请求的情况调用该函数,不会返回。直到有连接请求为止。二者存在逻辑上的先后关系
Linux 中,对套接字数据进行 I/O 时可以直接使用 I/O 相关函数;而在 Windows 中则不可以。原因为何?
Linux 把套接字也看作是文件,所以可以用文件 I/O 相关函数;而 Windows 要区分套接字和文件,所以设置了特殊的函数
创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?
套接字被创建之后,只有为其分配了IP地址和端口号后,客户端才能够通过IP地址及端口号与服务器端建立连接,需要调用 bind 函数来完成地址分配。分配地址是通过bind()
函数实现
Linux 中的文件描述符与 Windows 的句柄实际上非常类似。请以套接字为对象说明他们的含义。
Linux 的文件描述符是为了区分指定文件而赋予文件的整数值(相当于编号)。Windows 的文件描述符其实也是套接字的整数值,其目的也是区分指定套接字。
底层文件 I/O 函数与 ANSI 标准定义的文件 I/O 函数之间有何区别?
在ANSI标准中定义的I/O函数是作为C的标准提供的函数,无论操作系统如何,随时都可以调用。另一方面,低级文件I/O函数是操作系统提供的I/O函数。
参考本书给出的示例 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;
}
#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:
*******************************************/
#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!
*******************************************/
#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!
*******************************************/
#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!
*******************************************/
#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
*******************************************/