1. C/S模型和B/S模型
C/S模型:客户端/服务器模型
B/S模型:浏览器/服务器模型
2. TCP网络编程流程
![](https://i-blog.csdnimg.cn/blog_migrate/1880bf516ae936e74c4db8589256f488.png)
服务器:server
创建套接字 socket()
填充服务器网络信息结构体 struct sockaddr_in
将套接字与服务器的网络信息结构体绑定 bind()
将套接字设置为被动监听状态 listen()
阻塞等待客户端连接 accept()
进行通信 read()/write() 或 recv()/send() 或 recvfrom()/sendto()
关闭套接字 close()
客户端:client
创建套接字 socket()
填充服务器网络信息结构体 struct sockaddr_in
向服务器发送连接请求 connect()
进行通信 read()/write() 或 recv()/send() 或 recvfrom()/sendto()
关闭套接字 close()
3. 相关函数
3.1 socket()
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:
创建一个套接字,返回文件描述符
参数:
domain:
通信域,协议族
AF_UNIX 本地通信
AF_INET ipv4网络协议
AF_INET6 ipv6网络协议
AF_PACKET 底层协议
type:
套接字的类型
SOCK_STREAM 流式套接字 TCP
SOCK_DGRAM 数据报套接字 UDP
SOCK_RAW 原始套接字 用于链路层
protocol:
附加协议,如果只是单一协议,则设置为0
返回值:
成功:
文件描述符
失败:
-1
3.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的返回值
addr:
网络信息结构体
通用结构体:sockaddr (一般不用)
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
网络信息结构体:sockaddr_in (通过man 7 ip查询)
struct sockaddr_in {
sa_family_t sin_family; /* 地址族: AF_INET */
in_port_t sin_port; /* 网络字节序的端口号 */
struct in_addr sin_addr; /* ip地址 */
struct in_addr {
uint32_t s_addr; 无符号32为整形数据的ip地址
};
};
addrlen:
addr的大小
返回值:
成功:
0
失败:
-1
3.3 listen()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:
将套接字设置为被动监听状态
参数:
sockfd:、
文件描述符,socket的返回值
backlog:
允许同时连接客户端的个数,一般设置为5、10
返回值:
成功:
0
失败:
-1
3.4 accept()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
阻塞等待客户端的连接
参数:
sockfd:
文件描述符,socket的返回值
addr:
被填充的客户端的网络信息结构体,服务器获取到的客户端的信息
addrlen:
addr的大小
返回值:
成功:
文件描述符
失败:
-1
3.5 connect()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:
向服务器发送连接请求
参数:
sockfd:
文件描述符,socket的返回值
addr:
服务器的网络信息结构体,需要客户端自己填充
addrlen:
addr的大小
返回值:
成功:
0
失败:
-1
4. TCP服务器和客户端连接代码
4.1 服务器代码
//TCP网络编程之服务器
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//__FILE__: 获取文件名
//__func__:获取函数名
//__LINE__:获取行号
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s-%s-%d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in serveraddr, clientaddr;
socklen_t addrlen = sizeof(struct sockaddr_in);
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
//inet_addr:将点分十进制ip地址转化为无符号32位整形数据
//htons:将主机字节序转化为网络字节序
//atoi:将数字型字符串转化为整形数据 "1568" --> 1568
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:将套接字与服务器网络信息结构体绑定
if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("bind error");
}
//第四步:将套接字设置为被动监听状态
if(listen(sockfd, 5) == -1)
{
ERRLOG("listen error");
}
//第五步:阻塞等到客户端的连接
if(accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen) == -1)
{
ERRLOG("accept error");
}
//输出客户端的信息
printf("[%s - %d]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
return 0;
}
4.2 客户端代码
//TCP网络编程之客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//__FILE__: 获取文件名
//__func__:获取函数名
//__LINE__:获取行号
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s-%s-%d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(struct sockaddr_in);
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
//inet_addr:将点分十进制ip地址转化为无符号32位整形数据
//htons:将主机字节序转化为网络字节序
//atoi:将数字型字符串转化为整形数据 "1568" --> 1568
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:向服务器发送连接请求
if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("connect error");
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/59b544aa470610520e7529a339705510.png)