动手复现之前的网络编程项目发现有些知识有些遗忘,所以重读《TCP/IP 网络编程》学习整理一些笔记。
一、linux 下网络编程的部分API 函数及结构体
linux 下使用:man 函数名 //查看具体函数使用方法以及参数
1.API函数
//创建
1.socket
//服务端
2.bind
3.listen
4.accept
//客户端
5.connect
//I/O操作
6.send
7.recv
8.close
//其他
9.htons/ntohs
10.inet_addr/inet_ntoa
11.getpeername
...这里只罗列了一些基础可以构建简单通信的API,具体其他函数参照:Linux-API帮助文档
2.结构体
//通用地址结构
struct sockaddr{
ushort sa_family;//指代协议族,常见形式是 “AF_xxx”
char sa_data[14];//是14字节协议地址
};
上述结构体无法区分port与ip,因此改进为以下结构体,struct sockaddr_in
是 struct sockaddr
的一个子结构体。因此在使用的时候可以直接使用sockaddr_in来定义变量,因为子结构体类型的指针可以被强制转换为指向父结构体类型的指针。
//专用地址结构 一般都用这个
头文件 <arpa/inet.h>
struct sockaddr_in{
short sin_family;//指代协议族,一般用AF_INET:IPV4
u_short sin_port;//存储端口号(使用网络字节顺序)
struct in_addr sin_addr;//存储 IP 地址,使用 in_addr 这个数据结构
char sin_zero[8];//是为了让 sockaddr 与 sockaddr_in 两个数据结构保持大小相同而保留的空字节。
};
//本地通信协议地址结构:
struct sockaddr_un
{
sa_family_t sun_family; //协议族
char sun_path[108]; //套接字文件路径
}
//表示一个32位的IPv4地址的结构体
struct in_addr {
in_addr_t s_addr;//in_addr_t一般为32位的unsigned int,其字节顺序为网络字节序,
//即该无符号数采用大端字节序;其中每8位表示一个IP地址中的一个数值;
};
参考链接:https://blog.youkuaiyun.com/hao745580264_dawei/article/details/124894495
二、这些函数是什么
1.socket()
create an endpoint for communication
头文件引用
#include <sys/types.h>
#include <sys/socket.h>
参数列表
int socket(int domain, int type, int protocol);domain:协议族,指定通信时用的协议族,常用如下
AF_UNIX, AF_LOCAL :Local communication,用于本地进程/线程间通信;
AF_INET :IPv4 Internet protocols,用于IPV4网络通信;
AF_INET6 :IPv6 Internet protocols,用于IPV6网络通信;... ... 可以使用 man socket命令在linux下查看参数其他项
type:
SOCK_STREAM :流式套接字,TCP;
SOCK_DGRAM :数据报套接字,UDP;
SOCK_RAW :原始(透传)套接字;
protocol:
通常填0,在type类型为SOCK_RAW时,需要该参数。
返回值:成功时返回套接字(文件描述符),失败返回-1。
参考连接:https://blog.youkuaiyun.com/Mr_XJC/article/details/106788694返回值:成功时返回套接字描述符,失败返回-1
2.bind()
绑定信息,一般在服务端绑定信息。
头文件引用
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
参数列表
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);sockfd:套接字 socket()得到文件描述符
指向 结构体sockaddr *addr的指针,用于存放连接过来的客户端的IP地址和端口号,实际使用时,如果不需要客户端的信息,可以直接填NULL;
结构体 sockaddr的大小
返回值:成功时返回0,失败返回-1
3.listen()
监听是否有客户端连接
头文件引用
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
参数列表
int listen(int sockfd, int backlog);sockfd:套接字 socket()得到文件描述符
backlog:指定最大等待连接数
返回值:成功时返回0,失败返回-1
4.accept()
被动等待客户端连接(阻塞),
阻塞等待客户端的连接请求,当由客户端请求连接时,该函数返回已经建立连接的新套接字(文件描述符),用于客户端和服务器通信;
头文件引用
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
参数列表
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd:套接字 socket()得到文件描述符
指向 结构体sockaddr *addr的指针,用于存放连接过来的客户端的IP地址和端口号,实际使用时,如果不需要客户端的信息,可以直接填NULL;
结构体 sockaddr的大小
返回值:成功时返回新的文件描述符,后面与客户端通信用的就是该返回的文件描述符,失败返回-1。
5.connect()
connect - initiate a connection on a socket
客户端连接服务器
头文件引用
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
参数列表
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);sockfd:通过socket()得到的文件描述符;
addr :指向struct sockaddr类型结构体变量的指针,用于填充服务端的IP与端口信息;
addrlen:struct sockaddr类型结构体变量所占内存空间大小;
返回值:成功时返回0,失败返回-1。
I/O操作API
ssize_t send(int sockfd, const void *buf, size_t len);
ssize_t recv(int sockfd, void *buf, size_t len;
//sockfd:accept成功后返回的套接字描述符
//buf:发送or接收缓冲区
//size_t len,要传输的字符大小
补充知识点_t

三、这些函数怎么用
1.函数调用流程
下图是一个c/s通信的简单流程图,函数的调用按照下列顺序进行。其中I/O操作的顺序可以根据具体情况指定顺序。
废话不多说,上代码:
服务器端程序
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#define MYPORT 8080 // 服务器绑定的端口
#define MAXMESSAGE 100
#define MAX_SIZE 10 // listen函数中连接最大数
int main()
{
// 创建套接字
int sockfd, con_fd; // 保存socket()函数返回值
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
printf("create socket error\n");
return 1;
}
printf("create socket successful!!\n");
struct sockaddr_in my_addr;
my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);
bzero(&(my_addr.sin_zero), 8); // 清零函数
// 绑定信息
int err_bind = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
if (err_bind == -1)
{
printf("绑定失败,%s\n", strerror(errno));
return 1;
}
// 监听是否有连接
if (listen(sockfd, MAX_SIZE) == -1)
{
printf("监听失败,%s\n", strerror(errno));
return 1;
}
else
{
printf("listening......\n");
}
while (1)
{
socklen_t sin_size = sizeof(struct sockaddr);
// 接受客户端的连接 正确返回新的套接字用于后续的通信
con_fd = accept(sockfd, (struct sockaddr *)&my_addr, &sin_size);
switch (con_fd)
{
case -1:
printf("连接失败,%s\n", strerror(errno));
break;
case 0:
printf("客户端关闭,%s\n", strerror(errno));
break;
default:
printf("连接成功,客户端套接字:%d\n", con_fd);
send(con_fd, "ok", 2, 0);
close(con_fd);
break;
}
}
close(sockfd);
return 0;
}
客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define DES_IP "127.0.0.1"
#define DES_PORT 8080
#define MAXMES 10
int main()
{
int sockfd;
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = inet_addr(DES_IP);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(DES_PORT);
bzero(&(dest_addr.sin_zero), 8);
// connect
// 初始化参数列表
char buf[MAXMES];
socklen_t dest_size = sizeof(struct sockaddr);
if (connect(sockfd,(struct sockaddr *)&dest_addr, dest_size) == -1)
{
printf("连接失败,%s", strerror(errno));
}
else
{
recv(sockfd, buf, MAXMES, 0);
printf("服务器说:%s", buf);
}
close(sockfd);
}
先运行服务器端后运行客户端。
linux:gcc -o ser ser.cpp
linux:gcc -o cil cil.cpp
linux:./ser
linux:./cil
简单示例结果: