第五天
文件IO
5、lseek :
lseek - 重新定位读/写文件偏移
lseek() 重新定位与关联的打开文件描述的文件偏移量,
根据指令将文件描述符 fd 指向参数偏移量
存在库:
#include <sys/types.h>#include <unistd.h>off_t lseek(int fd, off_t offset, int whence);
fd:文件描述符号
offset:偏移字节数
whence:偏移的位置
SEEK_SET: 从文件头部开始偏移offset个字节。
SEEK_CUR: 从文件当前读写的指针位置开始,增加offset个字节的偏移量。
SEEK_END: 文件偏移量设置为文件的大小加上偏移量字节。
返回值:成功返回当前指针距离文件的开始的字节数,失败返回-1;
示例:
自写代码(已标准化):#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>#include <stdio.h>#define BUFFER_SIZE 1024#define SRC_FILE_NAME "src_file"#define DEST_FILE_NAME "dest_file"//根据传入的参数来设置offset#define OFFSET (atoi(args[1]))int main(int argc, char*args[]){int src_file, dest_file;unsigned char buff[BUFFER_SIZE];int real_read_len, off_set;if (argc != 2){fprintf(stderr, "Usage: %s offset\n", args[0]);exit(-1);}src_file = open(SRC_FILE_NAME, O_RDONLY);dest_file = open(DEST_FILE_NAME, O_WRONLY | O_CREAT, S_IREAD | S_IWRITE ); //owner权限:rwif (src_file < 0 || dest_file < 0){fprintf(stderr, "Open file error!\n");exit(1);}off_set = lseek(src_file, -OFFSET, SEEK_END);//注意,这里对offset取了相反数printf("lseek() reposisiton the file offset of src_file: %d\n", off_set);while((real_read_len = read(src_file, buff, sizeof(buff))) > 0){write(dest_file, buff, real_read_len);}close(dest_file);close(src_file);return 0;}
网络编程
1、服务器:接受连接请求,提供服务
2、客户端:发起连接请求
tcp/ip mqtt
tcp/ip:有连接的,可靠的,数据以字节流形式发送。
TCP 用于从应用程序到网络的数据传输控制。
TCP 负责在数据传送之前将它们分割为 IP 包,然后在它们到达的时候将它们重组。
ip:区分不同主机的依据
搭建tcp服务器
1、创建并打开套接字socket
2、绑定IP地址和端口号bind
3、创建监听队列listen
4、等待并建立连接accept
5、接收数据recv
6、关闭套接字close
1
、socket(获取套接字并设置属性)
socket() 创建一个通信端点并返回一个文件;
描述符指的是该端点。
成功调用返回的文件描述符将是当前未为进程打开的最小编号的文件描述符。
(man ip第七页有socket传参方式)
所在库:
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>
格式:
int socket(int domain, int type, int protocol);
domain: 领域
type :类型
protocol :协议
socket() creates an endpoint for communication and returns a file descriptor that
refers to that endpoint. The file descriptor returned by a successful call will be
the lowest-numbered file descriptor not currently open for the process.
socket() 创建通信端点并返回引用该端点的文件描述符。成功调用返回的文件描述符将是当前未为进程打开的最小编号的文件描述符。
The domain argument specifies a communication domain; this selects the protocol family
which will be used for communication.
域参数指定通信域;这选择协议族,
将用于通信。
RETURN VALUE
On success, a file descriptor for the new socket is returned. On error, -1 is re
turned, and errno is set appropriately.
成功后,将返回新套接字的文件描述符。出错时,返回 -1,并适当设置 errno。
2、bind(绑定设置服务器的端口号)
当使用 socket(2) 创建套接字时,它存在于名称空间(地址族)中
但没有为其分配地址。
bind()将addr指定的地址分配给
文件描述符 sockfd 引用的套接字。
addrlen指定大小,in
addr 指向的地址结构的字节数。传统上,此操作
称为“为套接字分配名称”。
涉及库:
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>
格式:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
课堂代码:
这段代码要通过两个终端页面执行:
服务器:
hqyj@ubuntu:~/desktop/s$ cat -n a.c1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/socket.h>5 #include<netinet/in.h>6 #include<netinet/ip.h>7 #include<arpa/inet.h> //引入必要的头文件,包括标准输入输出库、UNIX socket 库和网络地址库。8910 int main()11 {12 //创建套接字 在 main()函数中,调用socket()函数创建一个新的套接字,并将其保存在sockfd变量中。13 int sockfd = socket(AF_INET,SOCK_STREAM,0);14 if(0>sockfd) //检查socket()函数的返回值,如果小于0,则表示创建套接字失败.程序将输出一个错误消息并退出。15 {16 perror("socket");17 return -1;18 } //如果 socket() 函数成功返回,程序将输出套接字的文件描述符(即 sockfd 变量的值)。19 printf("sockfd:%d\n",sockfd);20 //2 bind 创建一个 sockaddr_in 结构体,并填充它的成员变量以指定要绑定的本地 IP 地址和端口号。21 struct sockaddr_in addr;22 addr.sin_family = AF_INET; //调用 bind()函数将套接字绑定到指定的本地 IP 地址和端口号上。23 addr.sin_port = htons(8888); //将主机序转化为网络字节序(htons函数)24 addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //将点分式Ip地址(字符串)转化成unsigned int型//检查 bind() 函数的返回值,如果小于 0,则表示绑定失败。程序将输出一个错误消息并退出。25 if(0>bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)))26 {27 perror("bind");28 return -1;29 }30 //打印ip地址 已存储于addr中,并且被转化成整形,用函数反向转化31 printf("ip:%s\n",inet_ntoa(addr.sin_addr));32 printf("端口号(port):%d\n",ntohs(addr.sin_port));333435 //监听模式 listen调用 listen() 函数开始监听来自客户端的连接请求。36 if(0 >listen(sockfd,5))37 {38 perror("listen");39 return -1;40 }41 printf("listen success\n");4243 //4.accept 接受请求 会将程序中断在此,直到有服务器连接44 struct sockaddr_in client;45 int len =sizeof(client);// 调用 accept() 函数接受客户端的连接请求,并返回一个新的套接字。46 int clientfd = accept(sockfd,(struct sockaddr *)&client,&len);47 if( 0 > clientfd) //如果accept()函数成功返回,程序将输出客户端的 IP 地址和端口号。48 {49 perror("accept");50 return -1;51 }52 printf("连接入的ip:%s\n连接端口号:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));5354 close(sockfd); //调用 close() 函数关闭套接字,释放资源。55 close(clientfd);56 return 0;57 }
客户端:hqyj@ubuntu:~/desktop/s$ cat c.c#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<netinet/ip.h>#include<arpa/inet.h>int main(){//创建打开套接字socketint sockfd = socket(AF_INET,SOCK_STREAM,0);if(0>sockfd){perror("socket");return -1;}printf("sockfd:%d\n",sockfd);//2 bindstruct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8888); //将主机序转化为网络字节序addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //将点分式Ip地址(字符串)转化成unsigned int型if(0>connect(sockfd,(struct sockaddr *)&addr,sizeof(addr))){perror("connect");return -1;}while(1);close(sockfd); //调用 close() 函数关闭套接字,释放资源。return 0;}
运行结果:先运行服务器,再运行客户端代码服务器:hqyj@ubuntu:~/desktop/s$ ./a.outsockfd:3ip:127.0.0.1端口号(port):8888listen success连接入的ip:127.0.0.1连接端口号:43938
客户端:hqyj@ubuntu:~/desktop/s$ ./c.outsockfd:3
解读:
1. 引入必要的头文件,包括标准输入输出库、UNIX socket 库和网络地址库。
2. 在 main() 函数中,调用 socket() 函数创建一个新的套接字,并将其保存在 sockfd 变量中。
3. 检查 socket() 函数的返回值,如果小于 0,则表示创建套接字失败。程序将输出一个错误消息并退出。
4. 如果 socket() 函数成功返回,程序将输出套接字的文件描述符(即 sockfd 变量的值)。
5. 创建一个 sockaddr_in 结构体,并填充它的成员变量以指定要绑定的本地 IP 地址和端口号。
6. 调用 bind() 函数将套接字绑定到指定的本地 IP 地址和端口号上。
7. 检查 bind() 函数的返回值,如果小于 0,则表示绑定失败。程序将输出一个错误消息并退出。
8. 如果 bind() 函数成功返回,程序将输出绑定的 IP 地址和端口号。
9. 调用 listen() 函数开始监听来自客户端的连接请求。
10. 检查 listen() 函数的返回值,如果小于 0,则表示监听失败。程序将输出一个错误消息并退出。
11. 如果 listen() 函数成功返回,程序将输出一个成功监听的消息。
12. 调用 accept() 函数接受客户端的连接请求,并返回一个新的套接字。
13. 检查 accept() 函数的返回值,如果小于 0,则表示接受连接请求失败。程序将输出一个错误消息并退出。
14. 如果 accept() 函数成功返回,程序将输出客户端的 IP 地址和端口号。
15. 调用 close() 函数关闭套接字,释放资源。
3.listen(监听客户端的连接请求)
Listen() 将 sockfd 引用的套接字标记为被动套接字,即作为
将用于使用accept(2) 接受传入连接请求的套接字。
存在库:
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>
格式:
int listen(int sockfd, int backlog);
sockfd:新套接字的文件描述符
backlog:客户端最大连接数
4.accept(接受客户端的连接请求)
accept() 系统调用与基于连接的套接字类型(SOCK_STREAM、
SOCK_SEQPACKET)。它提取侦听套接字 sockfd 的挂起连接队列上的第一个连接请求,创建一个新的已连接套接字,并返回引用该套接字的新文件描述符。新创建的socket不处于监听状态。原来的套接字sockfd不受此调用的影响。
所在库
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>
格式
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
The argument sockfd is a socket that has been created with socket(2), bound to a local
address with bind(2), and is listening for connections after a listen(2).
参数 sockfd 是一个使用 socket(2) 创建的套接字,使用 bind(2) 绑定到本地地址,并在listen(2)之后监听连接。
The argument addr is a pointer to a sockaddr structure. This structure is filled in
with the address of the peer socket, as known to the communications layer.
The exact
format of the address returned addr is determined by the socket's address
family (see
socket(2) and the respective protocol man pages). When addr is NULL, nothing is
filled in; in this case, addrlen is not used, and should also be NULL.
参数 addr 是指向 sockaddr 结构的指针。该结构填充有对等套接字的地址,如通信层所知。返回的地址 addr 的确切格式由套接字的地址族确定(请参阅套接字(2)和相应的协议手册页)。当addr为NULL时,不填任何内容;在这种情况下,addrlen 不被使用,并且也应该为 NULL。
The addrlen argument is a value-result argument: the caller must initialize it to con
tain the size (in bytes) of the structure pointed to by addr; on return it will con
tain the actual size of the peer address.
addrlen 参数是一个值-结果参数:调用者必须将其初始化以包含 addr 指向的结构的大小(以字节为单位);返回时它将包含对等地址的实际大小。
The returned address is truncated if the buffer provided is too small; in this case,addrlen will return a value greater than was supplied to the call.
如果提供的缓冲区太小,返回的地址将被截断;在这种情况下,addrlen 将返回一个大于提供给调用的值。
If no pending connections are present on the queue, and the socket is not marked as
nonblocking, accept() blocks the caller until a connection is present. If the socket
is marked nonblocking and no pending connections are present on the queue, accept()
fails with the error EAGAIN or EWOULDBLOCK.
如果队列中不存在挂起的连接,并且套接字未标记为非阻塞,则accept() 会阻塞调用者,直到存在连接。如果套接字被标记为非阻塞并且队列中不存在挂起的连接,则accept()
失败并出现错误 EAGAIN 或 EWOULDBLOCK。
In order to be notified of incoming connections on a socket, you can use select(2),
poll(2), or epoll(7).
A readable event will be delivered when a new connection is at
tempted and you may then call accept() to get a socket for that connection. Alterna
tively, you can set the socket to deliver SIGIO when activity occurs on a socket; see
socket(7) for details.
为了获得套接字上传入连接的通知,可以使用 select(2)、poll(2) 或 epoll(7)。当尝试新连接时,将传递一个可读事件,然后您可以调用accept()来获取该连接的套接字。或者,您可以将套接字设置为在套接字上发生活动时传递 SIGIO;详细信息请参见套接字(7)。
RETURN VALUE(返回值)
On success, these system calls return a nonnegative integer that is a file descriptor
for the accepted socket. On error, -1 is returned, errno is set appropriately, and
addrlen is left unchanged.
成功时,这些系统调用返回一个非负整数,它是已接受套接字的文件描述符。出错时,返回 -1,适当设置 errno,并且 addrlen 保持不变。
5,数据接收
所在库:
#include <sys/types.h>#include <sys/socket.h>
格式:
recv(sockfd, buf, len, flags);
1:ssize_t recv(int sockfd, void *buf, size_t len, int flags);2:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);3:ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
RETURN VALUE
These calls return the number of bytes received, or -1 if an error occurred. In the
event of an error, errno is set to indicate the error.
这些调用返回接收到的字节数,如果发生错误则返回 -1。如果发生错误,则会设置 errno 来指示错误。
When a stream socket peer has performed an orderly shutdown, the return value will be
0 (the traditional "end-of-file" return).
当流套接字对等方执行有序关闭时,返回值将为 0(传统的“文件结束”返回)。
Datagram sockets in various domains (e.g., the UNIX and Internet domains) permit zero
length datagrams. When such a datagram is received, the return value is 0.
各种域(例如 UNIX 和 Internet 域)中的数据报套接字允许零长度数据报。当接收到这样的数据报时,返回值为0。
The value 0 may also be returned if the requested number of bytes to receive from a
stream socket was 0.
如果从流套接字接收的请求字节数为 0,则也可能返回值 0。
5、read和write
同文件io
ead
用于服务器中时,当
read
返回
0
时,表示客户端已经退出
总合代码:实现客户端与服务器的输入数据通信
服务器:
hqyj@ubuntu:~/desktop/s$ cat -n a.c1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/socket.h>5 #include<netinet/in.h>6 #include<netinet/ip.h>7 #include<arpa/inet.h>8910 int main()11 {12 //给饭店起名字13 int sockfd = socket(AF_INET,SOCK_STREAM,0);14 if(0>sockfd)15 {16 perror("socket");17 return -1;18 }19 printf("sockfd:%d\n",sockfd);20 //2 bind21 struct sockaddr_in addr;22 addr.sin_family = AF_INET;23 addr.sin_port = htons(8888); //将主机序转化为网络字节序24 addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //将点分式Ip地址(字符串)转化成unsigned int型25 if(0>bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)))26 {27 perror("bind");28 return -1;29 }30 //打印ip地址 已存储于addr中,并且被转化成整形,用函数反向转化31 printf("ip:%s\n",inet_ntoa(addr.sin_addr));32 printf("端口号(port):%d\n",ntohs(addr.sin_port));333435 //监听 listen36 if(0 >listen(sockfd,5))37 {38 perror("listen");39 return -1;40 }41 printf("listen success\n");4243 //4.accept 接受请求44 struct sockaddr_in client;45 int len =sizeof(client);46 int clientfd = accept(sockfd,(struct sockaddr *)&client,&len);47 if( 0 > clientfd)48 {49 perror("accept");50 return -1;51 }52 printf("连接入的ip:%s\n连接端口号:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));5354 //发送数据55 while(1)56 {57 char buf[20]={0};58 gets(buf);59 write(clientfd,buf,sizeof(buf));60 }61 close(sockfd);62 close(clientfd);63 return 0;64 }
客户端:
hqyj@ubuntu:~/desktop/s$ cat -n c.c1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/socket.h>5 #include<netinet/in.h>6 #include<netinet/ip.h>7 #include<arpa/inet.h>8910 int main()11 {12 //给饭店起名字13 int sockfd = socket(AF_INET,SOCK_STREAM,0);14 if(0>sockfd)15 {16 perror("socket");17 return -1;18 }19 printf("sockfd:%d\n",sockfd);20 //2 bind21 struct sockaddr_in addr;22 addr.sin_family = AF_INET;23 addr.sin_port = htons(8888); //将主机序转化为网络字节序24 addr.sin_addr.s_addr = inet_addr("127.0.0.1");//将点分式Ip地址(字符串)转化成unsigned int型25 if(0>connect(sockfd,(struct sockaddr *)&addr,sizeof(addr)))26 {27 perror("connect");28 return -1;29 }30 while(1)31 {32 char buf[20]={0}; //服务器发多少就收到多少33 read(sockfd,buf,sizeof(buf)); //读满40才结束34 puts(buf);35 }36 close(sockfd);37 return 0;38 }
结果:
服务器端显示连接成功后,可以输入数据,客户端将收到数据
课后练习:
(已标准化)
服务器:
#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#define PORT_NUM 9999 //端口号定义#define BUF_SIZE 1024 //定义数组格式int main(){// 创建套接字int server_fd = socket(AF_INET, SOCK_STREAM, 0);// 设置服务器地址和端口struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT_NUM);server_addr.sin_addr.s_addr = INADDR_ANY;// 绑定服务器地址和端口bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));// 开始监听listen(server_fd, 1); //客户端最大连接数为1// 接收客户端连接struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);// 接收客户端消息并打印 数据接收char buf[BUF_SIZE];ssize_t recv_size;while ((recv_size = recv(client_fd, buf, BUF_SIZE, 0)) > 0) {printf("Received message from client: %s", buf);}// 关闭连接close(client_fd);close(server_fd);return 0;}
客户端:
#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#define SERVER_ADDR "127.0.0.1"#define PORT_NUM 9999#define BUF_SIZE 1024int main(){// 创建套接字int client_fd = socket(AF_INET, SOCK_STREAM, 0);// 设置服务器地址和端口struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT_NUM);server_addr.sin_addr.s_addr = inet_addr(SERVER_ADDR);// 连接服务器connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));// 发送消息while(1){char buf[BUF_SIZE] = {0};gets(buf);send(client_fd, buf, sizeof(buf), 0);};// 关闭连接close(client_fd);return 0;}