目录
sockt介绍
socket类型
socket所在位置
端口号(重点)
端口号作用
端口号范围
字节序(面试常见)
大端序
小端序
验证当前主机字节序
字节序转换和IP转换函数接口(常用)
TCP编程
函数接口
socket(重点)
bind
listen
accept(阻塞函数)
recv:能判断客户端是否中断
connect
send
练习:编写一个客户端和服务器程序,客户端发送消息,服务器接收并打印
server
client
sockt介绍
Socket是一种编程接口(API),用于在计算机网络中实现进程间的通信。它提供了一组函数,允许开发人员创建网络应用程序,通过网络传输数据并进行网络通信。
通过Socket,应用程序可以使用不同的网络协议(如TCP、UDP)进行通信,并在不同的计算机之间进行数据交换。Socket提供了一种抽象层,隐藏了底层网络细节,使开发人员能够专注于应用程序的逻辑,而不必过于关注底层网络编程细节。
- 是一个编程接口
- 是一种特殊的文件描述符
- 不只局限于TCP和IP协议
- 面向连接和无连接
总而言之,Socket提供了一种通用的网络编程接口,使开发人员能够创建网络应用程序,并实现跨计算机的数据传输和网络通信。它为开发各种类型的网络应用提供了基础设施和工具。
socket类型
流式套接字(SOCK_STREAM) TCP
特点:面向连接,可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM) UDP
特点:面向无连接,数据包以独立的形式发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
原始套接字(SOCK_RAM)
特点:对较低层次协议如IP和ICMP访问。
socket所在位置
Socket通常位于操作系统网络协议栈中。具体而言,Socket位于传输层(Transport Layer)和网络层(Network Layer)之间。
它是应用程序与底层网络协议之间的接口,充当了应用程序与网络之间的桥梁。
端口号(重点)
端口号作用
- 端口号用于标识网络中特定的进程或服务,实现进程间的通信和数据交换。它是网络通信中的重要组成部分,确保数据包被正确地传递到目标进程或服务。
- TCP的端口号与UDP端口号独立
- 端口号用两个字节表示 2byte
- 端口号的管理由Internet Assigned Numbers Authority(IANA,互联网编号分配机构)负责。IANA是一个全球性的组织,负责分配和管理全球互联网资源的唯一性和可用性,其中包括IP地址、域名和端口号等。
端口号范围
- 知名端口(Well-known Ports):范围从0到1023,用于标识特定的协议或服务。这些端口号通常与广泛使用的协议和服务相关联。如HTTP(端口号80)、FTP(端口号21)、SSH(端口号22)等。这些端口号的分配和管理由IANA直接负责。
- 注册端口(Registered Ports):范围从1024到49151,用于用户自定义的应用程序或服务。这些端口号可以由开发人员和组织自行申请并注册,以确保不同的应用程序之间不发生冲突。IANA维护了一个注册端口号列表,记录了已被分配的端口号及其分配给的应用程序或服务。
- 动态端口(Dynamic or Private Ports):范围从49152到65535,用于动态分配给客户端应用程序。当客户端应用程序需要进行网络通信时,操作系统会自动分配一个未使用的动态端口号。动态端口号的使用是临时的,通常在通信结束后被释放。
字节序(面试常见)
大端序
大端序(Big Endian):在大端序中,高位字节存储在低地址,低位字节存储在高地址。类似于人类读写数字的方式,从左到右依次表示更高位到更低位。例如,十六进制数0x12345678在大端序中的存储顺序为0x12 0x34 0x56 0x78。
小端序
小端序(Little Endian):在小端序中,低位字节存储在低地址,高位字节存储在高地址。与大端序相反,字节的存储顺序与数值的表示方式相同,从左到右依次表示更低位到更高位。例如,十六进制数0x12345678在小端序中的存储顺序为0x78 0x56 0x34 0x12。
验证当前主机字节序
#include <stdio.h>
union test{
int a;
char b;
short c;
};
int main()
{
int a=0x123456;
//1.指针强转法
char*p=(char*)&a;
printf("%x\n",*p);//56
printf("%x\n",*(p+1));//34
//2.数据类型强转
printf("%x\n",(char)a);//56
printf("%x\n",(short)a);//3456
//3.union共用体方法
union test s1;
s1.a=0x123456;
printf("%x\n",s1.b);//56
printf("%x",s1.c);//3456
return 0;
}
一般主机是小端序,网络是大端序。
字节序转换和IP转换函数接口(常用)
主机字节序到网络字节序(host to network)
u_long htonl (u_long hostlong);
u_short htons (u_short short); //掌握这个
网络字节序到主机字节序(network to host )
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
IP转换
in_addr_t inet_addr(const char *cp); //"Internet address"的缩写
//从人看的ip地址转为机器使用的32位无符号整数
char *inet_ntoa(struct in_addr in);//"Internet network to ASCII"的缩写
//从机器到人
TCP编程
注:上图主要是一种思想,实际编程思路如下函数接口
TCP服务器创建步骤:
socket:创建用于链接的套接字
bind:绑定套接字
listen:监听套接字,将主动套接字转换为被动套接字
accept:(重点:阻塞)等待客户端链接,链接成功返回一个链接套接字
recv:接收消息(根据实际情况决定顺序)
send:发送消息(根据实际情况决定顺序)
close:关闭socket和accept创建的套接字
TCP客户端创建步骤:
socket:创建用于链接的套接字
connect:(重点:阻塞)等待服务器链接
recv:接收消息(根据实际情况决定顺序)
send:发送消息(根据实际情况决定顺序)
close:关闭socket创建的套接字
函数接口
socket(重点)
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
domain:协议族
AF_UNIX, AF_LOCAL 本地通信
AF_INET ipv4
AF_INET6 ipv6
type:套接字类型
SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
protocol:协议 - 填0 自动匹配底层 ,根据type
系统默认自动帮助匹配对应协议
传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
返回值:
成功 文件描述符
失败 -1,更新errno
bind
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:绑定 ipv4 ip和端口
参数
sockfd:文件描述符
addr:通用结构体,根据socket第一个参数选择的通信方式最终确定这需要真正填充传递的结构体是那个类型。强转后传参数。
addrlen:填充的结构体的大小
返回值:0 失败-1、更新errno
使用网络编程需要用到的结构体:
通用结构体:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
ipv4结构体:
struct sockaddr_in {
sa_family_t sin_family; //协议族AF_INET
in_port_t sin_port; //端口
struct in_addr sin_addr;
};
ipv4结构体中存在一个struct in_addr 结构体:
struct in_addr {
uint32_t s_addr; //IP地址
};
ipv6结构体:
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
struct in6_addr {
unsigned char s6_addr[16];
};
listen
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
sockfd:套接字
backlog:同时响应客户端请求链接的最大个数,不能写0.
不同平台可同时链接的数不同,一般写6-8个
(队列1:保存正在连接)
(队列2,连接上的客户端)
返回值:成功 0 失败-1,更新errno
accept(阻塞函数)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept(sockfd,NULL,NULL);
阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
Sockfd :套接字
addr: 链接客户端的ip和端口号
如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小
如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:文件描述符; //用于通信
失败:-1,更新errno
recv:能判断客户端是否中断
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据
参数:
sockfd: acceptfd ;
buf 存放位置
len 大小
flags 一般填0,相当于read()函数
MSG_DONTWAIT 非阻塞
返回值:
< 0 失败出错 更新errno
==0 表示客户端退出
>0 成功接收的字节个数
connect
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
sockfd:socket函数的返回值
addr:填充的结构体是服务器端的;
addrlen:结构体的大小
返回值
-1 失败,更新errno
正确 0
send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:socket函数的返回值
buf:发送内容存放的地址
len:发送内存的长度
flags:如果填0,相当于write();
练习:编写一个客户端和服务器程序,客户端发送消息,服务器接收并打印
server
//server
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define N 128
typedef struct msg
{
int a;
char buf[N];
} msg_t, *msg_p;
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket err.");
exit(-1);
}
struct sockaddr_in caddr, saddr;
caddr.sin_family = AF_INET;
caddr.sin_port = htons(1026);
caddr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr *)&caddr, sizeof(caddr)) < 0)
{
perror("bind err.");
exit(-1);
}
if (listen(sockfd, 3) < 0)
{
perror("listen err.");
exit(-1);
}
socklen_t len = sizeof(saddr);
while (1)
{
int acceptid = accept(sockfd, (struct sockaddr *)&saddr, &len);
if (accept < 0)
{
perror("accept");
exit(-1);
}
printf("%d\n", ntohs(saddr.sin_port)); //打印链接端口
printf("%s\n", inet_ntoa(saddr.sin_addr)); //打印iP
msg_t s1;
memset(&s1, 0, sizeof(msg_t));
while (1)
{
int recvnum = recv(acceptid, &s1, sizeof(msg_t), 0);
if (recvnum < 0)
{
perror("recv err.");
exit(-1);
}
else if (recvnum == 0)
{
perror("recv err.");
break;
}
printf("%s \n", s1.buf);
}
close(acceptid);
}
close(sockfd);
return 0;
}
client
//client
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#define N 128
typedef struct msg
{
int a;
char buf[N];
} msg_t, *msg_p;
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket err.");
return -1;
}
struct sockaddr_in caddr;
caddr.sin_family = AF_INET;
caddr.sin_port = htons(1026);
caddr.sin_addr.s_addr = INADDR_ANY;
if (connect(sockfd, (struct sockaddr *)&caddr, sizeof(caddr)) == -1)
{
perror("connect err.");
return -1;
}
msg_t s1;
memset(&s1, 0, sizeof(msg_t));
while (1)
{
fgets(s1.buf, sizeof(s1.buf), stdin);
if (s1.buf[strlen(s1.buf) - 1] == '\n')
{
s1.buf[strlen(s1.buf) - 1] = '\0';
}
send(sockfd, &s1, sizeof(msg_t), 0);
}
close(sockfd);
return 0;
}