1 理解网络编程和套接字
套接字是网络数据传输用的软件。
TCP套接字可以看做一个电话机☎️,接收电话需要电话机。同理,接收网络数据就需要一个套接字。
bind函数:分配电话号码(IP地址和端口号)
listen函数:电话开机,准备接听(将socket设置为可接收连接状态)
accetp函数:接听电话
综上可以总结一下服务端套接字创建和接收消息的过程:
- 调用socket创建套接字;
- 调用bind分配IP和端口;
- 调用listen将套接字转为可接收请求状态;
- 调用accept处理请求;
```c
include
include
include
include
include
include
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 <port>\n", argv[0]);exit(1);}//调用 socket 函数创建套接字serv_sock = socket(PF_INET, SOCK_STREAM, 0);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]));//调用 bind 函数分配ip地址和端口号if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)error_handling("bind() error");//调用 listen 函数将套接字转为可接受连接状态if (listen(serv_sock, 5) == -1)error_handling("listen() error");clnt_addr_size = sizeof(clnt_addr);//调用 accept 函数受理连接请求。如果在没有连接请求的情况下调用该函数,//则不会返回,直到有连接请求为止clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);if (clnt_sock == -1)error_handling("accept() error");//稍后要将介绍的 write 函数用于传输数据,//若程序经过 accept 这一行执行到本行,则说明已经有了连接请求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); }
用户端发送请求步骤:1. 调用socket创建套接字;1. 调用connect连接指定IP + Port;1. 返回响应;```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);}//创建套接字,此时套接字并不马上分为服务端和客户端。//如果紧接着调用 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]));//调用 connect 函数向服务器发送连接请求if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)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);}
2 基于Linux的文件操作
在Linux中,socket被视为文件的一种。
文件创建成功会返回一个大于2的文件描述符(0、1、2为标准输入、输出、错误),socket也不例外,创建失败则返回-1。
打开文件
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int open(const char *path, int flag);/*成功时返回文件描述符,失败时返回-1path : 文件名的字符串地址flag : 文件打开模式信息*/
文件打开模式如下表:
| 打开模式 | 含义 |
|---|---|
| O_CREAT | 必要时创建文件 |
| O_TRUNC | 删除全部现有数据 |
| O_APPEND | 维持现有数据,追加到其后面 |
| O_RDONLY | 只读打开 |
| O_WRONLY | 只写打开 |
| O_RDWR | 读写打开 |
关闭文件
#include <unistd.h>int close(int fd);/*成功时返回 0 ,失败时返回 -1fd : 需要关闭的文件或套接字的文件描述符*/
写入文件
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t nbytes);/*成功时返回写入的字节数 ,失败时返回 -1fd : 显示数据传输对象的文件描述符buf : 保存要传输数据的缓冲地址nbytes : 要传输数据的字节数*/
在此函数的定义中,size_t 是通过 typedef 声明的 unsigned int 类型。对 ssize_t 来说,ssize_t 前面多加的 s 代表 signed ,即 ssize_t 是通过 typedef 声明的 signed int 类型。
读取文件
#include <unistd.h>ssize_t read(int fd, void *buf, size_t nbytes);/*成功时返回接收的字节数(但遇到文件结尾则返回 0),失败时返回 -1fd : 显示数据接收对象的文件描述符buf : 要保存接收的数据的缓冲地址值。nbytes : 要接收数据的最大字节数*/
