socket编程API

本文是socket编程API的学习记录,介绍了服务器和客户端使用socket通信的流程,包括创建、绑定、监听、连接等操作。还详细讲解了socket编程中各函数的功能、参数及返回值,如socket()、bind()等,以及网络字节序与主机字节序的转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

socket编程API——学习记录

tcp流程.png
服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
服务器为socket绑定ip地址和端口号
服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
客户端创建socket
客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求:
客户端连接成功,向服务器发送连接状态信息
服务器accept方法返回,连接成功
客户端向socket写入信息
服务器读取信息
客户端关闭
服务器端关闭

如何唯一标识网络中的一个进程 :

TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

socket();

int socket(int domain, int type, int protocol);
/例子/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

函数功能:根据指定的地址族、数据类型和协议来分配一个socket的描述字及其所用的资源。
domain:协议族,常用的有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE其中AF_INET代表使用ipv4地址

type:socket类型,常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等

protocol:协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等 (可以是“0”表示自动识别协议

返回值: sockfd 成功 //返回这个套接字的文件描述符 sockfd

​ -1 失败

bind():

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数功能:把一个地址族中的特定地址赋给socket

sockfd:服务器的socket描述字,也就是socket的引用 。// sockfd -> server_fd
addr:要绑定给sockfd的协议地址(服务器/端的地址) //addr -> server_addr

addrlen:地址的长度 socklen_t addrlen的常用形式:sizeof(struct sockaddr)

返回值: 0 绑定成功
​ -1 绑定失败

(1) 通用套接字数据结构(它可以在不同的协议族之间进行强制转换)
/*套接字地址结构,常用于bind、connect、recvfrom、sendto等函数的参数*/
struct sockaddr{            
sa_family_t  sa_family;      /*协议族*/  
char         sa_data[14];    /*协议族数据*/  
};

/*as_family_t类型是unsigned short类型,因此成员变量sa_family是16字节*/
typedef unsigned short  sa_family_t;
(2) 以太网中地址结构:struct sockaddr_in (in表示internet,网络地址)
    /*Internet以太网套接字地址结构*/
struct sockaddr_in {            
short int      sin_family;    /*通信协议,如:AF_INET(IPV4),通常与socket函数的domain一致*/
unsigned short int    sin_port;        /*2字节,16位的端口号,网络字节序*/
struct in_addr        sin_addr;     /*IP地址,32位*/                
unsigned char       sin_zero[8];     /*未用*/  
} 

struct in_addr{
  unsigned long  s_addr; /*32位IP地址,网络字节序*/    
};

/常用地址设置方法/

define MYPORT 3490 /端口地址/

struct sockaddr_in my_addr; /以太网套接字地址结构/
my_addr.sin_family = AF_INET; /地址结构的协议族/
my_addr.sin_port = htons(MYPORT); /端口地址转换为网络字节序/
my_addr.sin_addr.s_addr = inet_addr(“192.168.1.150”); /将字符串形式的IP地址转换为网络字节序/
~~~

注:sockaddr 和 sockaddr_in的相互关系

一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数

  • ​ sockaddr_in用于socket定义和赋值
  • ​ sockaddr用于函数参数

网络字节顺序 (Network Byte Order) NBO
结构体的sin_port和sin_addr都必须是NBO
本机字节顺序 (Host Byte Order) HBO
一般可视化的数字都是HBO

(3) NBO与HBO之间的转换(网络字节序与主机字节序之间的转换)

inet_addr() 将字符串点数格式地址转化成无符号长整型(unsigned long s_addr s_addr;)

inet_aton()    将字符串点数格式地址转化成NBO 
inet_ntoa ()     将NBO地址转化成字符串点数格式 
htons()    "Host to Network Short" 
htonl()    "Host to Network Long" 
ntohs()    "Network to Host Short" 
ntohl()    "Network to Host Long" 

常用的是htons(),inet_addr()正好对应结构体的端口类型和地址类型

/*IPV4对应地址结构*/
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */  如:my_addr.sin_addr.s_addr = inet_addr("192.168.1.150")
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order .32位IP地址,网络字节序*/
};
/*ipv6对应地址结构*/ 
struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};

Unix域对应的地址结构

#### #define UNIX_PATH_MAX    108

struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char    sun_path[UNIX_PATH_MAX];      /* pathname */ 
};

listen();

int listen(int sockfd, int backlog);

函数功能:监听socket (服务端创建的socket

sockfd: 要监听的socket描述字,服务端socket产生的描述符 sockfd -> server_sockfd

backlog:相应socket可以排队的最大连接个数

返回值: 0 成功

​ -1 失败

connect();

int connect(int sockfd, const struct sockaddr *addr, socklen_t* addrlen);

函数功能:连接某个socket

sockfd: 客户端 的socket描述符 sockfd -> client_sockfd

addr: 服务器 的socket地址(addr -> server_addr包含要连接的服务器的 IP+端口) 连接到地址结构指定的服务器上

addrlen:是一个指针不是结构, socket地址的长度(第二个参数地址结构的长度)socklen_t addrlen=> sizeof(server_addr)

accept();

int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);

/*例子*/
int server_sockfd, client_sockfd;
struct sockaddr_in   client_addr;  /*客户端地址*/
int  addr_length;  /*用于保存网络地址长度*/

addr_length = sizeof(struct sockaddr_in);   /*地址长度*/
client_sockfd = accept(servr_sockfd, &client_addr, &addr_length ); /*等待客户端连接,地址在client_addr中*/

TCP服务器监听到客户端请求之后,调用accept()函数取接收请求 返回值:client_sockfd

sockfd:服务器的socket描述字 sockfd -> server_sockfd 称为监听描述符

addr:客户端的socket地址 addr -> client_addr

addrlen:socket地址的长度

返回值: client_fd 成功,返回新连接的客户端套接字文件描述符 函数send()和recv()通过这个文件描述符进行数据收发

​ -1 失败

网络I/O操作有下面几组:read()/write() 、recv()/send()、readv()/writev()、recvfrom()/sendto()、recvmsg()/sendmsg()

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()
/*read()和write()函数*/
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

#include <sys/types.h>
#include <sys/socket.h>
/*send()和recv()函数*/ 
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

/*sendto()和recvfrom()函数*/
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

/*sendmsg()和recvmsg()函数*/
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
片

read();

ssize_t read(int fd, void *buf, size_t count);
功能:读取socket内容

fd:socket描述字

buf:缓冲区

count:缓冲区长度

返回值:成功,返回实际所读的字节数;返回“0”表示已读到的文件结束了

​ <0,出错 若错误为“EINTR”表示由中断引起,“ECONNREST”表示网络连接出错

write();

ssize_t write(int fd, const void *buf, size_t count);
功能:向socket写入内容,其实就是发送内容
fd:socket描述字
buf:缓冲区
count:缓冲区长度
返回值: 成功,返回写入字节数,>0,表示写入了部分或是全部的数据
<0,错误, “EINTR”表示写入时发生了中断,“PIPE”表示网络连接出错(对方已关闭连接)

send();

ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags); //flags参数值为“0”

sockfd:指定发送端套接字描述符。正在监听端口的套接字描述符
buff: 存放要发送数据的缓冲区
nbytes: 实际要改善的数据的字节数大小
flags: 一般设置为0
返回值: 成功,返回发送数据的长度,可以是0
​ -1,失败

flags参数

flags说明recvsend
MSG_DONTROUTE绕过路由表查找
MSG_DONTWAIT仅本操作非阻塞
MSG_OOB发送或接收带外数据
MSG_PEEK窥看外来消息
MSG_WAITALL等待所有数据

recv();

ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
sockfd: 接收端套接字描述符
buff: 用来存放recv函数接收到的数据的缓冲区
nbytes: 指明buff的长度
flags: 一般置为0

close();

int close(int fd);
服务器端:close(client_sockfd); //关闭客户端连接

socket标记为以关闭 ,使相应socket描述字的引用计数-1,当引用计数为0的时候,触发TCP客户端向服务器发送终止连接请求。

服务器地址清零:bzero() && memset()

bzero()

void bzero(void *s, int n);
参数:s为内存(字符串)指针,n 为需要清零的字节数。
bzero()会将参数s 所指的内存块(字符串)前n 个字节,全部设为零值。

bzero(void *s, int n); 《=等价于=》 memset((void*)s, 0,size_t n);

memset();—— (memset()函数的作用是清空数组)

void *memset(void *buffer, int c, int count);
或:void *memset(void *s, char ch, unsigned n);
void *memset(void *s,int c,size_t n);

buffer为指针或是数组,c是赋给buffer的值,count是buffer的长度.
这个函数在socket中多用于清空数组.
如:原型是memset(buffer, 0, sizeof(buffer))

注:不一定就是把内容全部设置为ch指定的ASCII值,而且该处的ch可为int或者其他类型,并不一定要是char类型。

1)void *memset(void *s,int c,size_t n)
总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。
2).memset() 函数常用于内存空间初始化。如:
char str[100];
memset(str,0,100); //全都初始化为0

3).memset可以方便的清空一个结构类型的变量或数组。

如:
  struct sample_struct{
           char csName[16];
           int iSeq;
           int iType;
           };

//对于变量:
struct sample_strcut  stTest;
//一般情况下,清空stTest的方法:
stTest.csName[0]='/0';
stTest.iSeq=0;
stTest.iType=0;
//用memset就非常方便:
memset(&stTest, 0, sizeof(struct sample_struct)); //初始化结构体

//如果是数组:
struct sample_struct TEST[10];
则
memset(TEST, 0, 10*sizeof(struct sample_struct)); //初始化数组
memset()函数,例子
#include <string.h>
 #include <stdio.h>
 #include <memory.h>
  int main(void)
  {
  char buffer[] = "Hello world/n";
  printf("Buffer before memset: %s/n", buffer);
  memset(buffer, '*', strlen(buffer) ); //数组直接首地址传进去。 主要是对数组指针的修改!!因为可以被修改而const char int等这些                                        //不能被修改 和malloc 配套使用
  printf("Buffer after memset: %s/n", buffer);
  return 0;
  }
  输出结果:
  Buffer before memset: Hello world
  Buffer after memset: ***********
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值