Socket网络编程

1、Socket网络编程流程

在这里插入图片描述

2、Socket网络编程(客户端)

一、socket

socket是什么:

  socket是一个套接字,也就是一个标准的通信接口

为什么要有socket:

  在早期的计算机网络中,通信使用的是不同的协议,这些协议之间缺乏标准化的接口,使得应用程序的编写和移植变得非常困难。

怎么创建一个socket:

头文件:
sockets.h
函数:
int socket(int af, int type, int protocol);
参数:
(1) af (Address Family):IP 地址类型
常见的有 AF_INET(值为2) 和AF_INET6(值为10)。
AF_INET是IPv4地址。
AF_INET6是IPv6地址。
(2) type:传输类型
常见的有SOCK_STREAM(1),SOCK_DGRAM(2),SOCK_RAW(3)。
SOCK_STREAM是流式套接口,面向连接的,底层是TCP协议;
SOCK_DGRAM是数据报套接口,无连接的,底层是UDP协议;
SOCK_RAW是原始套接口,用户自己定义底层协议,也就是在使用原始套接口发送数据时,需要提供完整的传输层头部,比如TCP、UDP或ICMP头部等。
(3) protocol:传输协议
常见的有IPPROTO_TCP(6),IPPTOTO_UDP(17),IPPROTO_ICMP(1),IPPROTO_IP(0)。
IPPROTO_TCP是TCP传输协议;
IPPTOTO_UDP是UDP传输协议;
IPPROTO_ICMP是ICMP传输协议;
IPPROTO_IP是IP传输协议,也就是说系统根据其他的参数自动推演出应该使用什么协议。
返回:
成功返回socket的文件套接字,失败返回-1。
案例:
(1) 创建有一个IP 地址类型为IPv4,传输类型是流式套接口的套接字:
int socketTcp = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
(2) 创建一个一个IP 地址类型为IPv4,传输类型是数据报套接口的套接字:
int socketUdp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

怎么关闭一个socket:

函数:
int shutdown(int sockfd,int howto);
作用:
  函数用于关闭套接字的读或写功能。它可以在套接字关闭之前,确保所有的数据都已经发送或接收完毕。
参数:
(1) sockfd:套接字描述符
(2) howto:功能参数。
可为SHUT_RD(0),SHUT_WR(1),SHUT_RDWR(2)。
SHUT_RD:关闭套接字的读功能,即禁止从套接字中读取数据。
SHUT_WR:关闭套接字的写功能,即禁止向套接字中写入数据。
SHUT_RDWR:关闭套接字的读写功能,即禁止从套接字中读取数据和向套接字中写入数据。

函数:
int close(int sockfd);
作用:
  关闭套接字的所有功能,包括读、写和监听。它会立即关闭套接字,并释放所有相关资源。如果在关闭套接字之前还有未发送的数据,这些数据会被丢弃。
参数:
(1) sockfd:套接字描述符。

使用建议:
  在关闭套接字时,应该先关闭读功能,再关闭写功能,最后再使用 close() 函数关闭套接字本身。

二、connect

connect是什么:

  connect是一个在标准库里的API函数

connet有什么用:

  顾名思义,connet函数起连接作用,帮助客户端连接服务端

怎么使用connect:

头文件:
sockets.h
函数:
int connect(int sock, struct sockaddr *addr, socklen_t addrlen);
参数:
(1)sock:套接字的文件描述符
(2)addr:sockaddr结构体变量的指针。
(3)addrlen:addr变量的大小
返回:
  成功返回0,失败返回-1,并且设置errno。

三、套接字地址结构体

(1)sockaddr

sockaddr是什么:
  sockaddr是一种通用的套接字地址结构体,也就是说可以用于保存多种类型的IP和端口,但在实际开发中由于该结构体没有具体的字段来存储相应的信息,所以一般都会使用更加详细的套接字地址结构体去赋值,然后再强制类型转化回去。
sockaddrx的原型:
struct sockaddr
{
  u8_t sa_len; //长度(一个字节)
  sa_family_t sa_family; //地址类型(一个字节)
  char sa_data[14]; // sa_family 类型的IP地址和端口号
};

(2)sockaddr_in

sockaddr_in是什么:
  sockaddr_in 是一个用于表示 IPv4 地址的套接字地址结构体,总字节数和sockaddr一样,可以进行强制类型转化成sockaddr。
sockaddr_in的原型:
struct in_addr
{
  in_addr_t s_addr; // 4个字节
};
struct sockaddr_in
{
  u8_t sin_len; //长度(一个字节)
  sa_family_t sin_family; //地址类型(一个字节,一般为AF_INET)
  in_port_t sin_port; //端口号(两个字节)
  struct in_addr sin_addr; //IP地址(4个字节)
  char sin_zero[8]; //不使用(8个字节)
};
字段解释:
sin_len:这个结构体的长度
   一般不写也可以。
sin_family:IP地址类型
   常见的有 AF_INET(2) 和 AF_INET6(10),这里使用AF_INET。
sin_port:端口号
   这里使用的是网络字节序(也就是大端字节序,主机字节序指正常的设备中存储数据的方式,可能是大端字节序也可能是小端字节序,所以在给端口号赋值的时候要注意使用函数将主机字节序转化网络字节序)。
sin_addr:IP地址
  这里使用的IP地址是32位的大端网络字节序(我们一般的IP地址使用的是点分十进制的字符串,所以要使用函数将点分十进制的字符串转化为32位数据)。
sin_zero[8]:闲置的
   该字段只是为了兼容sockaddr_in字节数和sockaddr一样,可忽略。
使用到的转化函数:

(1)uint16_t htons(uint16_t host);

功能:
将 16 位无符号整数从主机字节序转换为网络字节序,用于上述端口号的转换。
参数:
host,主机字节序的端口号
返回:
网络字节序的端口号
其他同类型函数:
Htonl():
将 32 位无符号整数从主机字节序转换为网络字节序。
ntohl():
将 32 位无符号整数从网络字节序转换为主机字节序。
ntohs():
将 16 位无符号整数从网络字节序转换为主机字节序。

(2)uint32_t inet_addr(const char *ip);

功能:
将点分十进制的IP地址字符串转化成网络字节序的32位数据。
参数:
ip,点分十进制的IP地址字符串。
返回:
网络字节序的32位数据。
其他同类型函数:
inet_aton():
将点分十进制的IPv4的IP地址字符串转换32位大端网络字节序整数。
inet_ntoa():
将一个网络字节序的32位整数转换为点分十进制的IPv4的IP地址字符串。
inet_pton():
将点分十进制的IPv4或IPv6的IP地址字符串转换32位大端网络字节序整数。
inet_ntop():
将一个网络字节序的32位整数转换为点分十进制的IPv4或IPv6的IP地址字符串。

(3)sockaddr_in6

sockaddr_in6是什么:
  是一个用于表示 IPv6 地址的套接字地址结构体,总字节数和sockaddr不一样,但在使用中也可以进行强制类型转化成sockaddr。
sockaddr_in6的原型:
struct in6_addr
{
 union
 {
  u32_t u32_addr[4];
  u8_t u8_addr[16];
 }
};
struct sockaddr_in6
{
 u8_t sin6_len; //长度(一个字节)
 sa_family_t sin6_family; //地址类型(一个字节,一般为AF_INET6)
 in_port_t sin6_port; //端口号(两个字节)
 u32_t sin6_flowinfo; //IPv6流信息(四个字节)
 struct in6_addr sin6_addr; //IPv6地址(16个字节)
 u32_t sin6_scope_id; //作用域的接口集(四个字节)
};

四、发送函数

(1)send

send是什么:
   send是一个在标准库里的API函数。
send有什么用:
   send把要发送的数据写到socket的缓存空间里。
send的工作逻辑:
  (1) 判断要发送的数据大小是否超过socket的缓存空间的大小,如果超过,返回错误,如果没有超过,进行下一步。
  (2) 判断socket中有没有正在发送的数据,如果有,就等待发送完再进入下一步,如果没有,进入下一步。
  (3) 在没有正在发送数据的前提下,判断是否有socket缓存空间的大小是否足够,如果够,就往缓存中写入数据,如果不够,等缓存发送完在写入
  (4) 如果写入过程中发生错误导致无法写入,会返回错误,否则返回发送的字节数
  (5) 如果在已经写入缓存完成的情况下,socket自动发送遇到未知错误导致无法发送时在下一次使用与socket相关的函数时会报错。
send原型:
头文件:
sockets.h
函数:
ssize_t send(int s, const void * dataptr, size_t size, int flags);
参数:
(1) s: 套接字的文件描述符
(2) dataptr: 要发送的数据
(3) size: 发送数据长度
(4) flags: 一般置0
返回:
  成功返回发送的字节数,失败返回-1,并且设置errno。

(2)write

write是什么:
  write是一个在标准库里的API函数。
write有什么用:
   把要发送的数据写到socket的缓存空间里,和send区别为send函数需要指定flags标识。
write原型:
头文件:
sockets.h
函数:
ssize_t write(int s, const void *data, size_t size);
参数:
(1) s: 套接字的文件描述符
(2) data: 要发送的数据
(3) size: 发送数据长度
返回:
  成功返回发送的字节数,失败返回-1,并且设置errno。

(3)sendto

sendto是什么:
   sendto是一个在标准库里的API函数。
sendto有什么用:
   sendto把要发送的数据写到socket的缓存空间里,和send区别为sendto函数在参数中带有目标套接字的地址,所以哪怕不进行connet这一步也可以使用sendto函数发送数据,一般用于UDP传输协议的socket中。在TCP传输协议的socket中,一般要进行connet,这时目标套接字地址参数为NULL即可。
sendto原型:
头文件:
sockets.h
函数:
ssize_t sendto(int s,const void *dataptr,size_t size,int flags,const struct sockaddr *to,socklen_t tolen)
参数:
(1)s: 套接字的文件描述符
(2)dataptr: 要发送的数据
(3)size: 发送数据长度
(4)flags: 一般置0
(5)to: 目标套接字地址
(6)tolen: 目标套接字地址长度
返回:
  成功返回发送的字节数,失败返回-1,并且设置errno。

(4)sendmsg

sendmsg是什么:
   sendmsg是一个在标准库里的API函数
sendmsg有什么用:
   sendmsg函数可以批量发送数据,有没有connet都可以使用。
sendmsg原型:
头文件:
sockets.h
函数:
ssize_t sendmsg(int s,const struct msghdr *message,int flags);
参数:
(1)s: 套接字的文件描述符
(2)message: 要发送的数据的信息结构体
(3)flags: 一般置0
msghdr结构体解析:
struct iovec
{
  void *iov_base; //要发送的某一组数据的首地址
  size_t iov_len; //要发送的某一组数据的长度
};
struct msghdr
{
  void *msg_name;
  socklen_t msg_namelen;
  struct iovec *msg_iov;
  int msg_iovlen;
  void *msg_control;
  socklen_t msg_controllen;
  int msg_flags;
};
(1) msg_name:放目标套接字的地址信息
(2) msg_namelen:目标套接字地址你信息的长度
(3) msg_iov:要发送数据的地址和长度信息的数组
(4) msg_iovlen:有多少组数据要发送
(5) msg_control:控制数据,可不写
(6) msg_controllen:控制数据长度,可不写
(7) msg_flags:标识符,一般为0

(5)writev

writev是什么:
   writev是一个在标准库里的API函数。
writev有什么用:
   writev函数可以批量发送数据,原理和sendmsg函数相似,但有connet才可以使用。
sendmsg原型:
头文件:
sockets.h
函数:
ssize_t writev(int s, const struct iovec *iov, int iovcnt)
参数:
(1)s: 套接字的文件描述符
(2)iov: 要发送的数据组结构体
(3)iovcnt: 要发送的数据组的数量

五、接收函数

(1)recv

recv是什么:
   recv是一个在标准库里的API函数。
recv有什么用:
   recv可以将socket接收缓存中的数据复制到指定的接收空间中。
recv的工作逻辑:
  (1) 判断socket是否在发送数据,如果是则等待发送完成再进行下一步,如果不是进行下一步。
  (2) 判断socket是否在接收数据,如果是则等待接收完成再进行下一步,如果不是进行下一步。
  (3) 判断socket的接收缓存中是否有数据,如果有则将接收缓存里面的数据复制到本地缓存空间(如果本地缓存空间没有接收缓存中数据那么大,那要进行多次接收),如果没有就继续等待。
recv原型:
头文件:
sockets.h
函数:
static ssize_t recv(int s, void * mem, size_t len, int flags);
参数:
(1)s: 套接字的文件描述符
(2)mem: 接收空间
(3)len: 接收空间的大小
(4)flags: 一般为0
返回:
  成功返回接收的字节数,失败返回-1,并且设置errno(在接收过程中对端关闭或网络中断,返回0)。

(2)read

read是什么:
   read是一个在标准库里的API函数。
read有什么用:
   read可以将socket接收缓存中的数据复制到指定的接收空间中,与recv的区别是read函数不需要指定flags标识。
read原型:
头文件:
sockets.h
函数:
ssize_t read(int s, void *mem, size_t len)
参数:
(1)s: 套接字的文件描述符
(2)mem: 接收空间
(3)len: 接收空间的大小
返回:
  成功返回接收的字节数,失败返回-1,并且设置errno(在接收过程中对端关闭或网络中断,返回0)。

(3)recvfrom

recvfrom是什么:
   recvfrom是一个在标准库里的API函数。
recvfrom有什么用:
   recvfrom可以将socket接收缓存中的数据复制到指定的接收空间中,与recv相比多了发送端的地址信息,一般在UDP协议下使用。
recvfrom原型:
头文件:
sockets.h
函数:
ssize_t recvfrom(int s, void *mem, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen)
参数:
(1)s: 套接字的文件描述符
(2)mem: 接收空间
(3)len: 接收空间的大小
(4)flags: 一般为0
(5)from: 发送端地址信息
(6)fromlen: 发送端地址信息长度
返回:
   成功返回接收的字节数,失败返回-1,并且设置errno。

3、Socket网络编程(服务端)

一、socket

参考Socket网络编程(客户端)

二、bind

bind是什么:
  connect是一个在标准库里的API函数。
bind有什么用:
  bind() 函数将套接字与特定的 IP 地址以及端口绑定,一般服务端会使用到,而客户端以服务器套接字绑定的IP、端口作为目标IP、端口进行通信。
怎么使用bind:
头文件:
sockets.h
函数:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
参数:
(1) sock:套接字的文件描述符
(2) addr:sockaddr结构体变量的指针
(3) addrlen:addr变量的大小

三、listen

listen是什么:
   listen是一个在标准库里的API函数。
listen有什么用:
   listen函数起监听socket的作用,也就是说当有客户端调用connect()发出连接请求时,服务器端可以知道有这个请求(这时服务端并没有接收这个请求)。
怎么使用listen:
头文件:
sockets.h
函数:
int listen(int s, int backlog);
参数:
(1)s: 套接字的文件描述符
(2)backlog: socket可以排队的最大连接个数
返回:
  成功返回0,失败返回-1,并且设置errno

四、accept

accept是什么:
   accept是一个在标准库里的API函数。
accept有什么用:
   在listen监听到有客户端发起连接请求后,accept函数将客户端信息绑定到一个新的socket上,通过返回值将新的socket的文件描述符传出(因为一个服务端socket会被很多客户端连接,而原始socket的文件描述符只有一个,所以要为每一个单独的客户端创建一个自己的socket文件描述符,后面和这个客户端的读写数据都是用这个文件描述符),同时将客户端的IP地址和端口号保存在第二个参数中。
怎么使用accept:
头文件:
socket.h
函数:
int accept(int s, struct sockaddr *addr, socklen_t *addrlen)
参数:
(1)s: 套接字的文件描述符
(2)addr: 用于储存接受到的客户端的网络信息的结构体
(3)addrlen: addr结构体长度
返回:
   发起请求的客户端的socket的文件描述符

五、read

参考Socket网络编程(客户端)

六、write

参考Socket网络编程(客户端)

七、close

参考Socket网络编程(客户端)

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一个简单的socket网络编程例子: 服务器代码: #include #include #include #include #pragma comment(lib,"ws2_32.lib") //这句话的意思是加载ws2_32.lib这个静态库 #define NETWORK_EVENT WM_USER+100 //如果你用mfc做开发,你可以点击菜单project-〉setting-〉link-〉object/library中添加这个静态库。 //如果你用c语言,你需要通过#pragma comment(命令来连接静态库 int main(int argc, char* argv[]){ HANDLE hThread = NULL; //判断是否输入了端口号 if(argc!=3){ printf("Usage: %sPortNumber\n",argv[1]); exit(-1); } //把端口号转化成整数 short port; if((port = atoi(argv[2]))==0){ printf("端口号有误!"); exit(-1); } WSADATA wsa; //初始化套接字DLL if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ //高字节指定了次版本号,低字节指定了主版本号,两个字节加到一起,就是你想要的Winsock库的版本号了 printf("套接字初始化失败!"); exit(-1); } //创建套接字 SOCKET serverSocket; if((serverSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){ printf("创建套接字失败!"); exit(-1); } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); serverAddress.sin_family=AF_INET; serverAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY); serverAddress.sin_port = htons(port); //绑定 if(bind(serverSocket,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR){ printf("套接字绑定到端口失败!端口: %d\n",port); exit(-1); } //进入侦听状态 if(listen(serverSocket,SOMAXCONN)==SOCKET_ERROR){ printf("侦听失败!"); exit(-1); } printf("Server %d is listening......\n",port); SOCKET clientSocket[5],maxSocket;//用来和客户端通信的套接字 struct sockaddr_in clientAddress;//用来和客户端通信的套接字地址 memset(&clientAddress,0,sizeof(clientAddress)); int addrlen = sizeof(clientAddress); fd_set fd_read; int i=0; int j; char buf[4096]; char buff[4096]="exit"; while(1) { FD_ZERO(&fd_read); maxSocket=serverSocket; FD_SET(serverSocket,&fd_read); //FD_SET(clientSocket[i-1],&fd_read); for(j=0;j<i;j++) { FD_SET(clientSocket[j],&fd_read); if(maxSocket"); //gets(buff); if(select(maxSocket+1,&fd_read,NULL,NULL,NULL)>0) { if(FD_ISSET(serverSocket,&fd_read)) { if(buff=="") { if((clientSocket[i++]=accept(serverSocket,(sockaddr*)&clientAddress,&addrlen))==INVALID_SOCKET) { printf("接受客户端连接失败!"); exit(-1); } else { for(j=0;j5) { printf("超过最大客户端数"); exit(-1); } } else { int bytes; for(int k=0;k<i;k++) { if(FD_ISSET(clientSocket[k],&fd_read)) { bytes=recv(clientSocket[k],buf,sizeof(buf),0); if(bytes==-1) { //listen(serverSocket,SOMAXCONN); for (int l=k;l<i;l++) clientSocket[l]=clientSocket[l+1]; i--; } /*if(bytes==0) { //printf("fdsdf"); listen(serverSocket,SOMAXCONN); for (int l=k;l0) { buf[bytes]='\0'; printf("Message from %s: %s\n",inet_ntoa(clientAddress.sin_addr),buf); if(send(clientSocket[k],buf,bytes,0)==SOCKET_ERROR) { printf("发送数据失败!"); exit(-1); } } } } } } } //清理套接字占用的资源 WSACleanup(); return 0; } 客户端代码: #include #include #include #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]){ //判断是否输入了IP地址和端口号 if(argc!=4){ printf("Usage: %s IPAddress PortNumber\n",argv[1]); exit(-1); } //把字符串的IP地址转化为u_long unsigned long ip; if((ip=inet_addr(argv[2]))==INADDR_NONE){ printf("不合法的IP地址:%s",argv[1]); exit(-1); } //把端口号转化成整数 short port; if((port = atoi(argv[3]))==0){ printf("端口号有误!"); exit(-1); } printf("Connecting to %s:%d......\n",inet_ntoa(*(in_addr*)&ip),port); WSADATA wsa; //初始化套接字DLL if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ printf("套接字初始化失败!"); exit(-1); } //创建套接字 SOCKET sock,serverSocket; if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){ printf("创建套接字失败!"); exit(-1); } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); serverAddress.sin_family=AF_INET; serverAddress.sin_addr.S_un.S_addr = ip; serverAddress.sin_port = htons(port); //建立和服务器的连接 if(connect(sock,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR) { printf("建立连接失败!"); exit(-1); } char buf[4096]; while(1){ printf(">"); //从控制台读取一行数据 gets(buf); if(send(sock,buf,strlen(buf),0)==SOCKET_ERROR){ printf("发送c数据失败!"); exit(-1); } int bytes; if((bytes=recv(sock,buf,sizeof(buf),0))==SOCKET_ERROR) { printf("接收c数据失败!\n"); exit(-1); } else { buf[bytes]='\0'; printf("Message from %s: %s\n",inet_ntoa(serverAddress.sin_addr),buf); } } //清理套接字占用的资源 WSACleanup(); return 0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值