1. TCP
全双工通信、面向连接、可靠
TCP(即传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。
高可靠原因: 1. 三次握手、四次挥手
2. 序列号和应答机制
3. 超时,错误重传机制
4. 拥塞控制、流量控制(滑动窗口)
适用场景
适合于对传输质量要求较高的通信
在需要可靠数据传输的场合,通常使用TCP协议
MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
2. TCP流程流程
服务器:
1.创建流式套接字(socket())------------------------> 有手机
2.指定本地的网络信息(struct sockaddr_in)----------> 有号码
3.绑定套接字(bind())------------------------------>绑定电话
4.监听套接字(listen())---------------------------->待机
5.链接客户端的请求(accept())---------------------->接电话
6.接收/发送数据(recv()/send())-------------------->通话
7.关闭套接字(close())----------------------------->挂机
客户端:
1.创建流式套接字(socket())----------------------->有手机
2.指定服务器的网络信息(struct sockaddr_in)------->有对方号码
3.请求链接服务器(connect())---------------------->打电话
4.发送/接收数据(send()/recv())------------------->通话
5.关闭套接字(close())--------------------------- >挂机
3. 要求
1. 客户端连接成功后进入循环发送状态,从终端获取用户输入并发送,当用户输入“quit”字符后退出循环并关闭客户端
2. 客户端输入quit退出后,服务器不退出,等待下一个客户端连接
循环服务器:一个服务器可以连接多个客户端,但是不能同时连接
3. 地址和端口都通过参数传入
4. 自动获取本机地址
5. 增加来电显示功能:显示客户端连入的地址
4. 服务器端
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
// 服务器:
// 1.创建流式套接字 socket()
// 2.指定本地的网络信息 struct sockaddr_in
// 3.绑定套接字 bind()
// 4.监听套接字 listen()
// 5.链接客户端的请求 accept()
// 6.接收/发送数据 recv()/send()
// 7.关闭套接字 close()
int main(int argc, char const *argv[])
{
int res;
// 1.创建流式套接字
int serverfd = socket(AF_INET, SOCK_STREAM, 0);
if (serverfd < 0)
{
perror("serverfd socket error");
;
return -1;
}
printf("server socket scuess\n");
// 2.指定本地的网络信息 struct sockaddr_in
// bind 第二个参数 struct sockaddr *addr
// 定义自己的ip地址,用ipv4的地址
// 绑定自己的地址
// 定义结构体变量 myaddr
struct sockaddr_in myaddr;
// 长度
socklen_t addrlen = sizeof(myaddr);
memset(&myaddr, 0, addrlen);
myaddr.sin_family = AF_INET; // ipv4族
// 结构体成员变量是sin_addr ,sin_addr是struct in_addr 结构体类型的,成员变量为 s_addr,
// 网络ip地址需要转成机器识别的类型;
// myaddr.sin_addr.s_addr = inet_addr("192.168.52.197");
myaddr.sin_addr.s_addr = INADDR_ANY;
// 端口
myaddr.sin_port = htons(atoi(argv[1]));
// 3.绑定套接字 bind()
// 绑定自己的地址
// int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 需要强转成通用结构体类型的地址
res = bind(serverfd, (struct sockaddr *)&myaddr, addrlen);
if (res < 0)
{
perror("bind error");
return -1;
}
printf("bind scuess\n");
// 4.监听套接字 listen()
// int listen(int sockfd, int backlog);
res = listen(serverfd, 5);
if (res < 0)
{
perror("listen error");
return -1;
}
printf("listen scuess\n");
// 5.链接客户端的请求 accept()
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 定义代表客户端的结构体变量
struct sockaddr_in acceptaddr;
int acceptfd;
while (1)
{
acceptfd = accept(serverfd, (struct sockaddr *)&acceptaddr, &addrlen);
if (acceptfd < 0)
{
perror("accept error");
return -1;
}
printf("acceptfd: %d \n", acceptfd);
printf("新的连接过来了\n");
printf("ip = %s, port = %d\n", inet_ntoa(acceptaddr.sin_addr),
ntohs(acceptaddr.sin_port));
// 6.接收/发送数据 recv()/send()
char buf[128] = "";
ssize_t num;
while (1)
{
// 每次接收前清空buf
memset(buf, 0, sizeof(buf));
num = recv(acceptfd, buf, sizeof(buf), 0);
// num为接收到的字符个数
if (num < 0) // 接收失败
{
perror("recv error");
return -1;
}
else if (num == 0) // 客户端退出
{
perror("client exit");
break;
}
else // 正常接收到字符
{
printf("buf: %s \n", buf);
}
}
}
close(serverfd);
close(acceptfd);
return 0;
}
5. 客户端
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
// 客户端:
// 1.创建流式套接字 socket()
// 2.指定服务器的网络信息 struct sockaddr_in
// 3.请求链接服务器 connect()
// 4.发送/接收数据 send()/recv()
// 5.关闭套接字 close()
int main(int argc, char const *argv[])
{
// 1.创建流式套接字
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
if (clientfd < 0)
{
perror("clientfd socket error");
;
return -1;
}
printf("client socket scuess\n");
// 2.指定服务器的网络信息 struct sockaddr_in
// 定义结构体变量 server_addr
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; // ipv4族
// 结构体成员变量是sin_addr ,sin_addr是struct in_addr 结构体类型的,成员变量为 s_addr,
// 网络ip地址需要转成机器识别的类型;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
// 端口
server_addr.sin_port = htons(atoi(argv[2]));
// 3.请求链接服务器 connect()
// 长度
socklen_t addrlen = sizeof(server_addr);
// 客户端的socket连接服务器
// int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 网络地址为 struct sockaddr_in 结构体类型 ,需要强转为 struct sockaddr * addr 类型
// 返回值为0,连接成功;返回值为-1,连接失败
int ret = connect(clientfd, (struct sockaddr *)&server_addr, addrlen);
if (ret < 0)
{
perror("connect err");
return -1;
}
printf("connect scuess\n");
// 4.发送/接收数据 send()/recv()
// 一旦连接服务器成功以后,从终端获取用户输入的数据,然后发送给服务器
char buf[128] = "";
ssize_t num;
while (1)
{
// 每次发送前清空数组
memset(buf, 0, sizeof(buf));
// 从终端读取内容
num = read(1, buf, sizeof(buf));
if (buf[strlen(buf) - 1] == '\n')
{
buf[strlen(buf) - 1] = '\0';
}
if (strcmp(buf, "quit") == 0)
{
break;
}
// 将终端读取的内容写到套接字中
write(clientfd, buf, num);
// 发送内容到服务器
send(clientfd, buf, num, 0);
}
close(clientfd);
return 0;
}
6. 运行结果
服务器端
客户机端