大学生寒假在家过于无聊,整理一下以前学过的知识,顺便复习一下,水平较低,专业性差,仅供参考,不喜勿喷(反正也没人看)。快解封了呜呜呜呜。。。。
一、TCP/IP协议簇
(1)概念
TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。
(2)组成
TCP/IP传输协议是严格来说是一个四层的体系结构,应用层、传输层、网络层和数据链路层都包含其中。
应用层协议:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
传输层协议:TCP,UDP
网络层协议:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层协议:SLIP,CSLIP,PPP,MTU
二、socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。复杂的TCP/IP协议族隐藏在Socket接口后面,我们只需要通过socket这个接口就可以了。
(1)socket概述
为了简化开发通信程序的工作,由Berkely学校开发了一套网络通信程序的API函数标准。
socket标准被扩展成window socket和unix socket。
linux中的网络编程通过socket接口实现。Socket既是一种特殊的IO,它也是一种文件描述符。一个完整的Socket 都有一个相关描述{协议,本地地址,本地端口,远程地址,远程端口};每一个Socket 有一个本地的唯一Socket 号,由操作系统分配。
(2)socket分类
流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。
数据报套接字(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。
原始套接字。
原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。
(3)套接字地址结构
TCP/IP协议族中,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。一下两个结构体中包含了这些必要的信息。
struct sockaddr
{
unsigned short sa_family; /* address族, AF_xxx */
char sa_data[14]; /* 14 bytes的协议地址 */
};
sa_family 一般来说, IPV4使用“AF_INET”。
sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的。
struct sockaddr_in {
short int sin_family; /* Internet地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* Internet地址 */
unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/
};
这两个数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便
(4)字节序列转换
因为每一个机器内部对变量的字节存储顺序不同(有的系统是高位在前,底位在后,而有的系统是底位在前,高位在后 ),而网络传输的数据大家是一定要统一顺序的。所以对与内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换。
htons()——“Host to Network Short”
主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes)
htonl()——“Host to Network Long”
主机字节顺序转换为网络字节顺序(对无符号长型进行操作4bytes)
ntohs()——“Network to Host Short”
网络字节顺序转换为主机字节顺序(对无符号短型进行操作2bytes)
ntohl()——“Network to Host Long ”
网络字节顺序转换为主机字节顺序(对无符号长型进行操作4bytes)
(5)地址格式转换
linux提供将点分格式的地址转于长整型数之间的转换函数。
inet_addr()能够把一个用数字和点表示IP 地址的字符串转换成一个无符号长整型。
inet_ntoa()
inet_aton()
(6)基本调用
socket() bind() connect()
listen() accept() send()
recv() sendto()
recvfrom() close()
三、代码实例
服务器与客户端
服务器可以接收客户端的消息并且反馈给客户端
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <string.h>
using namespace std;
int main()//服务器
{
//网络信息结构体
struct sockaddr_in s_addr,c_addr;
int len = sizeof(struct sockaddr_in);
socklen_t client_len= sizeof(struct sockaddr_in);
//socket 标识符
int socket_fd;
//accept 标识符
int accept_fd;
/*
参数1 协议族 iPv4 iPv6
参数2 TCP/UDP
参数3 默认0
*/
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0)
{
perror("socket error");
return 0;
}
//需要使用协议族 AF_INET 表示 IPv4 地址 AF_INET6 表示 IPv6 地址
s_addr.sin_family = AF_INET;
//设置端口号 因为大小端的问题 字节顺序转换
s_addr.sin_port = htons(10086);
//IP 地址 INADDR_ANY泛指本机
s_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(socket_fd, (struct sockaddr*)&s_addr, len) < 0)//绑定
{
perror("bind error");
return 0;
}
//第二个参数 服务器可以同时接收几个客户端
if (listen(socket_fd, 10) < 0)
{
perror("listen error");
return 0;
}
pid_t pid,ppid;
char buffer[50];
while (1)
{
if ((accept_fd = accept(socket_fd, (struct sockaddr*)&c_addr, &client_len)) == -1)
{
perror("accept error");
exit(1);
}
else
{
send(accept_fd, "连接至服务器!", strlen("连接至服务器!"), 0);
}
printf("\n%s:%d 登录服务器!\n\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
ppid = fork();
if (ppid == 0)//子进程 用于接收客户端的信息并发送反馈
{
int rec_size;
pid = fork(); //再次创建子进程
if (pid == 0) //子进程的子进程用于接收消息
{
while (true)
{
bzero(buffer, sizeof(buffer));
if ((rec_size = recv(accept_fd, buffer, sizeof(buffer), 0)) == -1)
{
perror("接收客户端信息错误");
}
else if (rec_size != 0)
{
buffer[rec_size] = '\0';
printf("%s:%d said:%s\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port), buffer);
if (send(accept_fd, buffer, rec_size, 0) == -1)//将客户端发送过来的消息发回给客户
{
perror("send error");
break;
}
}
}
}
else if(pid>0)
{
}
}
else if (ppid > 0)
{
close(accept_fd);
}
}
return 0;
}
客户端可以发送消息给服务器 并且服务器可以接收多个客户端
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <string.h>
using namespace std;
int main()
{
//网络信息结构体
struct sockaddr_in s_addr;
int len = sizeof(struct sockaddr_in);
//socket 标识符
int client_fd;
/*
参数1 协议族 iPv4 iPv6
参数2 TCP/UDP
参数3 默认0
*/
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0)
{
perror("socket error");
return 0;
}
//需要使用协议族
s_addr.sin_family = AF_INET;
//设置端口号 因为大小端的问题 字节顺序转换
s_addr.sin_port = htons(10086);
//IP 地址
s_addr.sin_addr.s_addr = inet_addr("192.168.52.130");
if (connect(client_fd,(struct sockaddr*)&s_addr, len) < 0)
{
perror("connect error");
return 0;
}
char buffer[50];
int num = 0;
while (1)
{
bzero(buffer, sizeof(buffer));
printf("\n开始 ...\n");
if ((num = recv(client_fd, buffer, sizeof(buffer), 0)) == -1)
{
perror("recv");
exit(1);
}
else if (num > 0)
{
int len, bytes_sent;
buffer[num] = '\0';
printf("收到: %s\n", buffer);
printf("发送:");
char msg[50] = { '\0'};
scanf("%s", msg);
//发送至服务器
if (send(client_fd, msg, sizeof(msg), 0) == -1)
{
perror("send error");
}
}
else
{
//numbytes=0,表示socket已断开
printf("socket 结束!\n");
}
}
close(client_fd);
return 0;
}
执行结果