tcp/ip协议族
名词 | 业务 | 描述 |
---|---|---|
应用层 | DNS、HTTP、FTP、PING | 软件程序 |
传输层 | TCP、UDP | 操作系统 |
网络层 | ICMP、IP、ARP | 操作系统 |
物理层 | 物理网络 双绞线、无线网、光纤 | 硬件设备 |
专有名词 作用
ACK 确认是否有效 值为1
seq 序列号标记位
SYN 请求建立连接 值为1
FIN 希望断开连接
TCP的三次握手
TCP的四次挥手
网络编程代码示例:
一、TCP简单通讯例子
TCP服务端代码
//服务端
#include <sys/types.h> //基本系统数据类型
#include <sys/socket.h> //socket套接字
#include <stdio.h>
#include <unistd.h> //POSIX系统api访问
#include <string.h>
#include <arpa/inet.h> //网络信息转换
#include <iostream>
using namespace std;
#define PORT 9990 //端口号
#define BUFFSIZE 1024 //接收数组大小
//创建套接字和初始化服务器地址以及监听函数
int create_and_init_socket()
{
//创建socket
/*
*int socket(int family,int type,int protocol),返回一个文件描述符
*family: 代表协议族,在socket中只能是AF_INET
* type: 代表协议类型常见类型是SOCK_STREAM(TCP)-字节流,SOCK_DGRAM(UDP)-数据报
* protocol: 代表具体的协议,对于标准套接字来说,其值是0。(原始套接字基本不会使用)
*/
int socket_listen = socket(AF_INET, SOCK_STREAM, 0);
if (socket_listen == -1) //创建监听失败
{
cout << "socket failed..." << endl;
return -1;
}
//初始化服务器地址
struct sockaddr_in server_dddr; //IPv4地址的数据结构
memset(&server_dddr, 0, sizeof(server_dddr));
server_dddr.sin_family = AF_INET; //Internet地址族
server_dddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址
server_dddr.sin_port = htons(PORT); //端口号
//绑定socket套接字
/*
*int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
*sockfd 代表需要绑定的socket。是在创建socket套接字时返回的文件描述符
*addr 存放了服务端用于通信的地址和端口
*addrlen 代表addr结构体的大小
*/
int re = bind(socket_listen, (struct sockaddr *) &server_dddr, sizeof(server_dddr));
if (re == -1)
{
cout << "bind failed..." << endl;
return -1;
}
//启动监听
/*
*int listen(int sockfd, int backlog);
*listen函数的功能并不是等待一个新的connect的到来,
*真正等待connect的是accept函数。
*listen的操作就是当有较多的client发起connect时,
*server端不能及时的处理已经建立的连接,
*这时就会将connect连接放在等待队列中缓存起来。
*这个等待队列的长度有listen中的backlog参数来设定。当listen运行成功时,返回0;运行失败时,返回值为-1.
* sockfd: socket创建的文件描述符;
* backlog: 指server端可以缓存连接的最大个数,也就是等待队列的长度。
*/
int ret = listen(socket_listen, 5);
if (ret == -1)
{
cout << "listen failed..." << endl;
return - 1;
}
return socket_listen;
}
//等待客户端连接
int wait_to_accept_client(int socket_)
{
//创建一个客户端IPv4地址的数据结构
struct sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
cout << "等待客户端连接..." << endl;
//接收客户端连接,创建一个和客户端交流的套接字
/*
*int accept(int sockfd,struct sockaddr *client_addr,socklen_t *addrlen);
*accept函数等待客户端的连接,如果没有客户端连上来,
*它就一直等待,这种方式称为阻塞。
*accept等待到客户端的连接后,创建一个新的socket,
*函数返回值就是这个新的socket,
*服务端用于这个新的socket和客户端进行报文的收发。
*
*sockfd: 是已经被listen过的socket
*client_addr: 用于存放客户端的地址信息,其中包含客户端的协议族,网络地址以及端口号。如果不需要客户端的地址,可以填0
*addrlen: 用于存放参数二(client_addr)的长度
*/
int client_socket = accept(socket_, (struct sockaddr *) &client_addr, (socklen_t *) &client_addr_len);
if (client_socket == -1)
{
cout << "accept failed..." << endl;
return -1;
}
cout << "连接到一个客户端..." << endl;
return client_socket;
}
//处理信息函数
void handel_client_infos(int listen_socket, int client_socket)
{
char buff[BUFFSIZE];
//循环接收发送信息
while (1)
{
//读取信息
/*
*int recv(int sockfd,void *buf,int len,int flags);
*recv函数用于接收对端socket发送过来的数据。
*不论是客户端还是服务端,
*应用程序都用recv函数接受来自TCP连接的另一端发送过来的数据。
*如果socket对端没有发送数据,recv函数就会等待,
*如果对端发送了数据,函数返回接收到的字符数。出错时返回-1。
*如果socket被对端关闭,返回值为0。
*
* sockfd: 接收端的套接字描述符,即通过socket()函数返回的文件描述符
* buf: 为用于接收数据的内存地址,可以是C语言基本数据类型变量的地址,
* 也可以是数组、结构体、字符串。只要是一块内存就行了。
* len: 指明需要接收数据的字节数。不能超过buf的大小,否则内存溢出。
* flags: 填0,其他数值意义不大
*/
/*
*ssize_t read(int fd, void *buf, size_t count);
*read函数会阻塞等待,直到有数据可读或者发生错误。
*它返回实际读取的字节数,如果返回值为0表示对端已关闭连接,返回-1表示发生错误。
* fd是文件描述符,
* buf是用于存储读取数据的缓冲区,
* count是要读取的字节数(字节数的读取需要小于缓冲区区域,避免内存溢出)
*/
int ret = read(client_socket, buff, BUFFSIZE);
if (ret == -1)
{
cout << "read failed..." << endl;
break;
}
if (ret == 0) //零输入
{
break;
}
//给字符数组添加结尾符'\0'
buff[ret] = '\0';
//for (size_t i = 0; i < ret; i++)
//{
//
//}
//cout << endl;
cout << "客户端:" << buff << endl;
//写入信息
/*
*ssize_t write(int fd, const void *buf, size_t count);
*write函数会阻塞等待,直到所有数据都写入完毕或者发生错误。
*它返回实际写入的字节数,返回-1表示发生错误
*fd是文件描述符,
*buf是要写入的数据的缓冲区,
*count是要写入的字节数
*/
char buf[BUFFSIZE];
cout << "你想输入(服务端):";
cin >> buf;
int ret2 = write(client_socket, buf, ret);
if (ret2 == -1)
{
break;
}
if (strncmp(buff, "end", 3 == 0)) //退出符号
{
break;
}
}
close(client_socket);
}
int main()
{
int listen_socket = create_and_init_socket();
int client_socket = wait_to_accept_client(listen_socket);
handel_client_infos(listen_socket, client_socket);
close(listen_socket);
return 0;
}
TCP客户端代码
//客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <iostream>
using namespace std;
#define PORT 9990
#define SIZE 1024
int main()
{
int client_socket = socket(AF_INET, SOCK_STREAM, 0); //创建和服务器连接套接字
if(client_socket == -1)
{
//perror("socket");
cout << "socket failed...";
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; /* Internet地址族 */
addr.sin_port = htons(PORT); /* 端口号 */
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */
inet_aton("127.0.0.1", &(addr.sin_addr)); //将一个IPv4地址的字符串表示转换为网络字节序的32位二进制数
int addrlen = sizeof(addr);
int listen_socket = connect(client_socket, (struct sockaddr *)&addr, addrlen); //连接服务器
if(listen_socket == -1)
{
//perror("connect");
cout << "connect failed...";
return -1;
}
printf("成功连接到一个服务器\n");
char buf[SIZE];
while(1) //向服务器发送数据,并接收服务器信息
{
printf("你想输入(客户端):");
//scanf("%s", buf);
cin >> buf;
write(client_socket, buf, strlen(buf));
int ret = read(client_socket, buf, strlen(buf));
//printf("服务端 = %s", buf);
//printf("\n");
cout << "服务端:" << buf << endl;
if(strncmp(buf, "end", 3) == 0) //当输入end时客户端退出
{
break;
}
}
close(listen_socket);
return 0;
}
二、UDP简单通讯例子
udp服务端代码
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
using namespace std;
#define PORT 8888
#define BUFFSIZE 1024
//简单UDP通讯程序,服务端接收
int main()
{
//创建套接字
int sock;
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("error");
return -1;
}
//创建地址协议数据结构体
struct sockaddr_in s;
memset(&s, 0, sizeof(s));
s.sin_family = AF_INET;
s.sin_port = htons(8888);
s.sin_addr.s_addr = htonl(INADDR_ANY);
//将套接字与协议进行绑定
if (bind(sock, (struct sockaddr *) &s, sizeof(s)) == -1)
{
perror("error");
return -1;
}
//接收并输出信息
char buff[BUFFSIZE];
socklen_t sock_len = sizeof(s);
/*
int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
(需要注意的是,这里recvfrom中最后一个长度参数传的是socklen_t *指针,而非sendto中的socklen_t)
recvfrom函数会阻塞程序执行,直到有数据到达或者发生错误。当有数据到达时,
它会将数据读取到buf指向的缓冲区中,并返回实际接收到的字节数。如果发生错误,它会返回-1,并设置errno来指示具体的错误原因。
参数:
sockfd:表示要接收数据的套接字描述符。
buf:指向接收数据的缓冲区。
len:表示接收缓冲区的大小。
flags:用于指定接收操作的行为,常用的标志有MSG_DONTWAIT和MSG_WAITALL等。
src_addr:指向一个sockaddr结构体,用于存储发送方的地址信息。
addrlen:指向一个整数,表示src_addr结构体的长度。
*/
recvfrom(sock, buff, sizeof(buff) /* - 1 */, 0, (struct sockaddr *)&s, (socklen_t *)&sock_len);
//printf("client message:%s\n", buff);
cout << "客户端信息:" << buff << endl;
return 0;
}
udp客户端代码
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
using namespace std;
#define PORT 8888
#define BUFFSIZE 1024
//简单UDP通讯程序,客户端发送
int main()
{
//创建socket套接字
int sock;
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) //使用数据报协议SOCK_DGRAM
{
perror("error");
return -1;
}
//创建协议地址数据结构体并进行初始化
struct sockaddr_in s;
memset(&s, 0, sizeof(s));
s.sin_family = AF_INET;
s.sin_port = htons(PORT);
s.sin_addr.s_addr = htonl(INADDR_ANY);
//输入信息并进行发送
char buff[BUFFSIZE];
//scanf("%s", buff);
cout << "你想输入(客户端):";
cin >> buff;
/*
*int sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
sendto函数的返回值为成功发送的字节数,如果返回-1,则表示发送失败,可以通过errno来获取具体的错误信息。
参数:
sockfd:表示要发送数据的套接字描述符。
buf:指向要发送的数据的缓冲区。
len:表示要发送的数据的长度。
flags:用于指定发送操作的可选标志,常用的标志有0和MSG_DONTWAIT。
dest_addr:指向目标地址的结构体指针,包括目标IP地址和端口号等信息。
addrlen:表示目标地址结构体的长度。
*/
sendto(sock,buff, strlen(buff), 0, (struct sockaddr *)&s, sizeof(s));
return 0;
}