套接字(Socket)编程(一) 函数概念篇

套接字是网络通信的基石,是网络通信的基本构建,最初是由加利福利亚大学Berkeley分校为UNIX开发的网络通信编程接口,本文主要介绍套接字的有关内容,了解使用套接字编写程序过程

一.概念

所谓的套接字,实际上是一个指向传输提供者的句柄。根据性质和作用的不同,套接字可以分为原始套接字、流式套接字和数据包套接字3种

  • 原始套接字:原始套接字是在WinSock2规范中提出的,它能够是程序开发人员对底层的网络传输机制进行控制,在原始套接字下接收到数据中包含IP头。
  • 流式套接字:流式套接字提供了双向、有序、可靠的数据传输服务,该类型套接字在通信前需要双方建立连接。TCP协议采用的就是流式套接字
  • 数据包套接字:与流式套接字对应的数据包套接字,数据包套接字提供了双向的数据流,但是它不能保证数据包传输的可靠性、有序性和无重复性。UDP协议采用的就是数据包套接字
1.TCP的套接字的socket编程

基于TCP面向连接的socket编程的服务器端程序流程如下:
① 创建套接字socket(socket
② 创建的套接字绑定(bind)到本地的地址和端口上
③ 设置套接字的状态为监听(listen),准备接受客户端的连接请求
④ 接受请求(accpet),同时返回得到一个用于连接的新套接字
⑤ 使用这个新套接字进行通信(通信函数使用send/recv
⑥ 通信完毕,释放套接字资源(close
基于TCP面向连接的socket编程的客户端程序流程如下:
① 创建套接字socket(socket
② 向服务器发出连接请求(connect
③ 请求连接后与服务器进行通信操作(send/recv
④ 释放套接字资源(close

注意:
在服务器的一端,当调用了accept函数时,程序就会进行等待,直到有客户端调用connect函数发送连接请求,然后服务器接受该请求,这样服务器与客户端就建立了连接;
在服务器端要建立套接字绑定到指定的主机IP和端口上等待客户的请求,但是对于客户端来说,当发起连接请求并被接受后,在服务器端就保存了该客户端的IP地址和端口号的信息。对于服务器端来说,一旦建立连接之后,实际上它已经保存了客户端的IP和端口号的信息了,因此可以利用返回的套接字进行与客户端的通信

2.UDP的套接字的socket编程

基于UDP面向无连接的socket编程接收端程序如下:
① 创建套接字socket (socket
② 将套接字绑定(bind)到一个本地地址和端口上
③ 等待接收数据(recvfrom
④ 释放套接字资源(close
基于UDP面向无连接的socket编程发送端程序如下:
① 创建套接字socket(socket
② 向服务器发送数据(sendto
③ 释放套接字资源(close

二.通信函数

头文件导入#include <sys/socket.h> #include <arpa/inet.h>
1.socket函数
函数:int socket(int, int, int);
功能:创建一个套接字
解释:int socket(int af, int type, int protocol);

  • af:表示一个地址家族,IPv4连接时填AF_INET,IPv6连接时对应AF_INET6
  • type:表示套接字类型,如果是SOCK_STREAM表示创建面向连接的流式套接字;如果是SOCK_DGRAM表示面向无连接的数据包套接字;为SOCK_RAW表示创建原始套接字
  • protocol:表示套接口所用的协议,如果用户不指定,可以设置为0
  • 返回值:int类型的一个指向传输提供者的句柄(套接字)

示例:int socket = socket(AF_INIT, SOCK_STREAM,0);创建一个对应IPv4的TCP套接字

2.bind函数
函数:int bind(int, const struct sockaddr *, socklen_t)
功能:将套接字绑定到指定端口和地址上
解释:int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);

  • sockfd:表示套接字标识

  • my_addr:是一个指向struct sockaddr结构体类型的指针,一般绑定 IPv4地址传入struct sockaddr_in指针,IPv6地址传入struct sockaddr_in6指针(PS:这里我也不是很清楚,sockaddr_in结构体里面有地址族,改变地址族为IPv6的不知道行不行,下面的演示和注释以前面的struct sockaddr_in结构体为主),都包含地址和端口号信息,强转成需要的类型,结构体具体参数如下

    sockaddr_in结构体解释.png

    //示例:
    struct sockaddr_in addrs;
    addrs.sin_len = sizeof(addrs);
    addrs.sin_family = AF_INET;
    addrs.sin_port = htons(5000);
    addrs.sin_addr.s_addr = htonl(INADDR_ANY);
    
  • addrlen:确定my_addr缓冲区的长度,就是前面结构体的大小

  • 返回值:如果函数执行成功,则返回值为0,失败则为-1

示例:int result = bind(socket, (struct sockaddr *)&addrs,sizeof(addrs));将套接字绑定到主机地址,端口号为5000的端口上

3.listen函数
函数:int listen(int, int)
功能:将套接字设置为监听模式,对于流式套接字,必须处入监听模式才能够接收客户端套接字的连接
解释:int listen(int sockfd, int backlog);

  • sockfd:表示套接字标识
  • backlog:表示等待连接的最大队列长度。例如:如果backlog被设置为2,此时有3个客户端同时发出连接请求,那么前两个客户端会放置在等待连接队列中,第三个客户端会得到错误信息。
  • 返回值:成功则返回0,失败则返回-1

示例:listen(socket, 5);设置套接字为监听状态,为连接做准备,最大等待的连接缓冲为5

4.accept函数
函数:int accept(int, struct sockaddr * __restrict, socklen_t * __restrict)
功能:接受客户端的连接,在流式套接字中,只有在套接字处入监听状态,才能接受客户端的连接
解释:int accept(int sockfd, struct sockaddr *addr, int *addrlen);

  • sockfd:表示套接字标识
  • addr:是一个sockaddr_in结构体指针,接收连接端地址信息
  • addrlen:前面sockaddr_in结构体地址长度指针,用来接收连接端地址信息结构体长度
  • 返回值:一个新的套接字,它对应于已经接受的客户端连接,对于该客户端的所有后续操作,都应使用这个新的套接字

示例:int socket_server = accept(socket, (struct scokaddr*)&client_addr,&addrlen);
socket_server保存接受请求后返回的新的套接字,socket_server为绑定在地址和端口上的套接字,而client_addr是有关连接端地址信息结构,最后的addrlen是client_addr的大小

5.close函数
函数:int close(int)
功能:关闭套接字
解释:int close(int sockfd);

6.connect函数
函数:int connect(int, const struct sockaddr *, socklen_t)
功能:发送一个连接请求
解释:int connect(int sockfd,const struct sockaddr * addr_server, int addrlen)

  • sockfd:表示一个正准备连接的套接字。客户端连接套接字int socket_client
  • addr_server:存储套接字要连接主机的地址信息结构体指针
  • addrlen:前面结构体的也就是addr_server结构体缓冲区的长度
  • 返回值:成功则返回0,失败则返回-1

示例:int result = connect(socket_client, (struct sockaddr *)&addr_server, sizeof(addr_server));

7.recv函数
函数:ssize_t recv(int, void *, size_t, int)
功能:从流式套接字中接受数据,平时开发针对TCP套接字接收数据
解释:

ssize_t recv(
             int sockfd,    //表示流式套接字
             void *buff,    //用来存放recv函数接收到的数据的缓冲区
             size_t nbytes, //缓冲区的长度
             int flags      //表示函数的调用方式,一般填0,具体看下面表格
             )
//成功时返回接收的字节数(收到EOF是返回0),失败是返回-1

示例:ssize_t recvLen = recv(socket, receivebuf, 100, 0);其中receivebuf是保存接收数据,后面的100是实际要改善接收数据的字节数

8.send函数
函数:ssize_t send(int, const void *, size_t, int)
功能:在流式套接字中发送数据
解释:

ssize_t send(
             int sockfd,        //表示流式套接字
             const void *buff,  //存放要发送数据的缓冲区
             size_t nbytes,     //实际要改善的数据的字节数
             int flags          //表示函数的调用方式,一般填0,具体看下面表格
             )
//返回值:成功返回发送的字节数,错误返回-1

示例:ssize_t sendLen = send(socket, sendbuf,100,0);其中sendbuf是保存要发送数据组

9.recvfrom函数
函数:ssize_t recvfrom(int, void *, size_t, int, struct sockaddr * __restrict, socklen_t * __restrict)
功能:用于接收一个数据包,并保存源地址
解释:

ssize_t recvfrom(
                 int sockfd,   //表示准备接收的套接字
                 void * buff,  //指向缓冲区的指针,用来接收数据
                 size_t nbytes, //表示缓冲区的长度
                 int flags,     //表示调用方式,一般填0
                 struct sockaddr * __restrict from, /* 是一个指向地址结构体的指针,用来接收发送数据方的地址信息 */
                 socklen_t * __restrict fromLen //前面结构体长度指针
                 )
//返回值:如果正确接收返回接收到的字节数,失败返回-1.

10.sendto函数
函数:ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t)
功能:向指定的目的地方发送数据
解释:

ssize_t sendto(
               int sockfd,        //表示准备发送数据的套接字
               const void * buff, //指向缓冲区的指针,该缓冲区包含将要发送的数据
               size_t nbytes,     //缓冲区数据长度
               int  flags,        //表示调用方式,一般填0
               const struct sockaddr *  to, //指向目标套接字地址的结构体指针
               socklen_t  tolen,  //前面结构体长度
               )
//成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。

linux版本flag参数:

flags 说明 recv send
MSG_OOB 用于传输外带数据
MSG_PEEK 窥看外来消息(验证数据缓冲中是否存在接收的数据)
MSG_DONTROUTE 绕过路由表查找(数据传输过程中不参考路由表, 在本地网络中寻找目的地)
MSG_DONTWAIT 调用I/O函数是不阻塞, 用于使用非阻塞I/O
MSG_WAITALL 等待所有数据(防止函数返回,直到接收全部请求的字节数)

三.辅助函数

1.htons() 函数ntohs()函数
函数:u_short htons(u_short hostshort)
功能:将一个16位的无符号端整形数据由主机排列方式转换成网络排列方式,所谓的网络排列方式就是大端排列方式,MacOS是采用小端的存储方式存储数据
使用地方:在有关主机地址和端口号结构体中struct sockaddr_in里面,属性in_port_t sin_port表示端口号,因为端口号要用网络排列方式,使用该函数转换后赋值

函数: u_short ntohs(u_short netshort)
功能: 与htons()功能相反,将16位无符网络排列端口转换成主机排列方式,也就是将16位大端排列数字转换成小端排列方式
使用地方: 得到地址结构体struct sockaddr_in,将里面的in_port_t sin_port转换成我们平时看到的小端排列的端口号

2.htonl() 函数ntohl()函数
函数:u_long htonl(u_long hostlong)
功能:将一个32位无符号整形由主机排列方式转换成网络排列方式,所谓的网络排列方式就是大端排列方式
使用地方:在有关主机地址和端口号结构体中struct sockaddr_in里面,结构体属性struct in_addr sin_addr中的in_addr_t s_addr属性表示IP地址信息,因为IP地址信息要用网络排列方式,使用该函数转换后赋值

//1.申明一个字符串IP地址
NSString *ip = @"192.168.1.1";
NSArray *ip_arr = [ip componentsSeparatedByString:@"."];
//2.将IP地址转换成32位4字节的无符整形数据
uint smart_ip = ([ip_arr[0] intValue] << 24) | ([ip_arr[1] intValue] << 16) | ([ip_arr[2] intValue] << 8) | [ip_arr[3] intValue];
NSLog(@"%u",smart_ip);
     /* ① 打印出来smart_ip: 3232235777 -> 0xC0A80101
      * ② 在Xcode里面查看smart_ip内存:0x7ffeefbff5cc : 01 01 A8 C0
      *    将指针指向的地址分成内存编号如下
      *    0x7ffeefbff5cc -> 01
      *    0x7ffeefbff5cd -> 01
      *    0x7ffeefbff5ce -> A8
      *    0x7ffeefbff5cf -> C0
      *    从上面可以看出在内存中,数据是小端存储方式(数据的高位存高位),
      *    所以说要是直接传入数字对面会解析成 1.1.168.192,这样地址接反过来了,所以需要转换
      */
//3.调用函数转换数据的排列方式
uint big_ip = htonl(smart_ip);
NSLog(@"%u",big_ip);
      /* ① 打印出来smart_ip: 16885952 -> 0x0101A8C0
       * ② 在Xcode里面查看smart_ip内存:0x7ffeefbff5c8 : C0 A8 01 01
       *    将指针指向的地址分成内存编号如下
       *    0x7ffeefbff5c8 -> C0
       *    0x7ffeefbff5c9 -> A8
       *    0x7ffeefbff5ca -> 01
       *    0x7ffeefbff5cb -> 01
       *    所以传入 16885952 对面会按大端解析成 192.168.1.1
       */
//我们知道在局部变量是在函数执行时分配到栈上的,栈是由高地址位向低址位扩展的,函数执行时"先提"升栈(这里的"提升"是说栈顶指针向低址位扩展),为函数执行在栈上分配缓存的空间,smart_ip是前面的参数,所以写进栈里面在较高的内存地址位,而big_ip是后面的参数,后写进栈里面,在低址位,所以要比smart_ip内存地址低

函数:u_long ntohl(u_long netlong)
功能:与函数 htonl()功能相反,将网络排列的32位无符数据转换成主机排列,就是将32位大端排列数字转换成小段排列数据
使用地方:得到地址结构体struct sockaddr_in,将里面的in_addr_t s_addr转换成我们平时看到的小端排列32位IP地址

3.inet_addr 函数
函数:in_addr_t inet_addr(const char *)
功能:将存储IP地址的char字符串转换成网络排列方式的32位无符号整形,跟上面htonl()函数功能一样
使用地方:转换的结果可直接用来给地址信息结构体里面的IP地址赋值,因为转换出来的结果是网络排列的

//声明地址信息结构体
struct sockaddr_in addr;
        
NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding]; //转换的结果就是 "192.168.1.60"
in_addr_t addr_ip = inet_addr(cChar_ip);
addr.sin_addr.s_addr = addr_ip; //结构体里面的IP地址赋值

4.inet_aton 函数
函数:int inet_aton(const char *, struct in_addr *);
功能:与函数inet_addr功能一样,将char字符串IP地址转换成网络排序的无符整形,传入struct in_addr结构体指针,直接赋给结构体
使用地方:

struct sockaddr_in addr;
        
NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding];
int result = inet_aton(cChar_ip, &addr.sin_addr);
if (result != 0) {
    printf("地址赋值成功\n");
}else {
    printf("地址赋值无效\n");
}
//函数返回非0表示cp主机有地有效,返回0表示主机地址无效

5.inet_pton 函数
函数:int inet_pton(int, const char *, void *);
功能:与辅助函数htonl()inet_addr()inet_aton()的功能一样,将char字符串IP地址转换成网络排序的无符整形,直接赋给struct in_addr结构体指针里面,不一样的是可以根据地址族的不同转换IPv6还是IPv4的地址
解释:

int inet_pton(
              int af,           //地址族 AF_INET 对应IPv4,AF_INET6对应IPv6
              const char *src,  //盛装C字符串格式的IP地址信息
              void *dst         //地址结构体里面 struct in_addr IP地址结构指针
              );
//返回值:若成功则为1,若输入不是有效的IP地址表达式则为0,若出错则为-1 

示例:

struct sockaddr_in addr;
        
NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding];
        
int result = inet_pton(AF_INET, cChar_ip, &addr.sin_addr);
if (result == 1) {
      printf("地址赋值成功\n");
}else if (result == 0) {
      printf("输入的地址无效\n");
}else {
      printf("转换出错\n");
}

6.inet_ntoa 函数
函数:char *inet_ntoa(struct in_addr)
功能:正好与上面的函数inet_aton功能相反,需要传入一个关于地址信息的结构体,解析出来C字符串的IP地址
使用地方及方式:

//接着辅助函数3/4/5的例子往下走
char *cChar_ip_out= inet_ntoa(addr.sin_addr);
        
NSLog(@"%s",cChar_ip_out);
NSLog(@"%@",[NSString stringWithCString:cChar_ip_out encoding:NSUTF8StringEncoding]);
/* 打印结果:
   192.168.1.60
   192.168.1.60
 */

所以这样从地址结构体里面获取IP地址,就可以用该函数直接可以将结构体里面的struct in_addrIP地址结构体转换成c字符串进行输出和打印

7.inet_ntop 函数
函数:const char *inet_ntop(int, const void *, char *, socklen_t);
功能:跟上面函数inet_ntoa()功能相似,于函数inet_pton()功能相反,不一样的是他可以传入地址族,传入AF_INET则解析出IPv4的地址,传入AF_INET6则解析出IPv6的地址
解释:

const char *inet_ntop(
                      int af,           //地址族 AF_INET 对应IPv4,AF_INET6对应IPv6
                      const void * src, //地址结构体里面 struct in_addr IP地址结构指针
                      char * dst,       //盛装C字符串格式的IP地址信息
                      socklen_t cnt     //C字符串的宽度
                      )
//返回值:若成功则为指向C字符串格式IP地址缓存的指针,若出错则为NULL  

示例:

//接着辅助函数3、4、5的例子往下走
char cChar_ip_out[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &addr.sin_addr, cChar_ip_out, (socklen_t)sizeof((cChar_ip_out))) == NULL) {
     printf("地址转换出错\n");
}else {
     printf("地址转换成功:%s \n",cChar_ip_out);
}

8.getpeername 函数
函数:int getpeername(int, struct sockaddr * __restrict, socklen_t * __restrict)
功能:获取socket套接字对方的地址信息,返回0时正常,否则错误
解释:int getpeername(int sockfd, struct sockaddr * peerAddr, socklen_t * addrLen)

  • sockfd:表示套接字
  • peerAddr:接收地址信息结构体,传入结构体指针外面申明里面赋值,获取到的是连接对象的地址信息
  • addrLen:地址信息结构体的长度指针
  • 返回值:返回0时可以通过地址信息指针取里面的信息,<0时错误

使用方式:

//IPv4连接,获取对方的IP地址
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
        
if (getpeername(sockfd, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
    NSLog(@"获取地址出错");
 }else {
            
    char addrBuf[INET_ADDRSTRLEN];
            
    if (inet_ntop(AF_INET, &sockaddr4.sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
    {
         addrBuf[0] = '\0';//表示字符结束
     }
            
    NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
    NSLog(@"获取的IPv4地址为:%@",ip);
}
//IPv6连接,获取对方的IP地址
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
        
if (getpeername(sockfd, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
    NSLog(@"获取地址出错");
}else {
            
    char addrBuf[INET6_ADDRSTRLEN];
            
    if (inet_ntop(AF_INET6, &sockaddr6.sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
    {
           addrBuf[0] = '\0';//表示字符结束
     }
            
    NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
    NSLog(@"获取的IPv6地址为:%@",ip);
}

8.getsockname 函数
函数:int getsockname(int, struct sockaddr * __restrict, socklen_t * __restrict)
功能:获取socket套接字的地址信息,返回0时正常,否则错误
解释:int getsockname(int sockfd, struct sockaddr * addr, socklen_t *addrLen)

  • sockfd:表示套接字
  • addr:接收地址信息结构体,传入结构体指针外面申明里面赋值,获取到的是sockfd的地址信息
  • addrLen:地址信息结构体的长度指针
  • 返回值:返回0时可以通过地址信息指针取里面的信息,<0时错误

使用方式

//IPv4连接,获取socket的IP地址
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
        
if (getsockname(sockfd, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
     NSLog(@"获取地址出错");
}else
{
            
     char addrBuf[INET_ADDRSTRLEN];
            
     if (inet_ntop(AF_INET, &sockaddr4.sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
     {
        addrBuf[0] = '\0';//表示字符结束
     }
NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
NSLog(@"获取socket的IPv4地址为:%@",ip); 
}
//IPv6连接,获取socket的IP地址
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
        
if (getsockname(sockfd, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
    NSLog(@"获取地址出错");
}else
{
    char addrBuf[INET6_ADDRSTRLEN];
            
    if (inet_ntop(AF_INET6, &sockaddr6.sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
    {
        addrBuf[0] = '\0';
    }
NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
NSLog(@"获取socket的IPv6地址为:%@",ip);
}

四.简单的代码实现

下面是关于套接字简单的实现,只做参考,可以进行简单的通信但是不能用作正式的项目开发(因为里面有太多的逻辑没做判断和参数设置),下面代码编写是基于Xcode Command Line Tool工程

  • TCP服务器和客户端代码实现
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int creatServer(void);
int creatClient(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int serv_result = creatServer();
        if (serv_result == 0) printf("开启服务器失败\n");
        
        //int clnt_result = creatClient();
        //if (clnt_result == 0) printf("开启客户端失败\n");
    }
    return 0;
}

#pragma mark ---服务器端套接字创建并开启监听
int creatServer()
{
    int serv_sockfd, clnt_sockfd;
    struct sockaddr_in serv_addr,clnt_addr;
    
    serv_addr.sin_len = sizeof(struct sockaddr_in);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(2000);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    serv_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (bind(serv_sockfd, (struct sockaddr*)&serv_addr, serv_addr.sin_len) < 0) return 0;
    if (listen(serv_sockfd, 1) < 0) return 0;
    
    socklen_t len_t = sizeof(struct sockaddr_in);
    clnt_addr.sin_len = len_t;
    clnt_sockfd = accept(serv_sockfd, (struct sockaddr*)&clnt_addr, &len_t);
    if (clnt_sockfd < 0) return 0;
    close(serv_sockfd);
    printf("有客户端连接 IP:%s Port:%d\n",inet_ntoa(clnt_addr.sin_addr),ntohs(clnt_addr.sin_port));
    
    char recv_buffer[512];
    while (1) {
        memset(recv_buffer, 0, 512);
        ssize_t recv_len = recv(clnt_sockfd, recv_buffer, sizeof(recv_buffer), 0);
        if (recv_len == 0) {
            printf("通信结束\n");
            break;
        }else if (recv_len < 0) {
            printf("读取数据出错\n");
            break;
        }else {
            printf("recv: %s\n",recv_buffer);
        }
    }
    printf("服务器关闭\n");
    close(clnt_sockfd);
    
    return 1;
}

#pragma mark ---客户端端套接字创建并开始连接
int creatClient()
{
    printf("请输入服务器IP地址:\n");
    char ip[INET_ADDRSTRLEN];
    scanf("%s",ip);
    
    int clnt_sockfd;
    clnt_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in clnt_addr;
    clnt_addr.sin_len = sizeof(struct sockaddr_in);
    clnt_addr.sin_family = AF_INET;
    clnt_addr.sin_port = htons(2000);
    inet_aton(ip, &clnt_addr.sin_addr);
    
    if (connect(clnt_sockfd, (struct sockaddr*)&clnt_addr, clnt_addr.sin_len) < 0) return 0;
    printf("连接成功: %s 输入 'C' 退出结束\n",ip);
    
    char send_buffer[512];
    while (1) {
        memset(send_buffer, 0, 512);
        printf("请输入需要发送的内容:");
        gets(send_buffer);
        size_t buffer_len = strlen(send_buffer);
        if (buffer_len == 0) continue;
        if (strcmp(send_buffer, "C") == 0) break;
        
        ssize_t send_len = send(clnt_sockfd, send_buffer, buffer_len, 0);
        if (send_len == buffer_len) {
            printf("发送成功: %zu %zd\n",buffer_len,send_len);
        }else {
            printf("发送失败\n");
            break;
        }
    }
    printf("客户端关闭\n");
    close(clnt_sockfd);
    
    return 1;
}
  • UDP发送端和接收端实现
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

int sendPacket(void);
int recvPacket(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int send_result = sendPacket();
        if (send_result == 0) printf("开启发送失败\n");
        
        //int recv_result = recvPacket();
        //if (recv_result == 0) printf("开启接收失败\n");
    }
    return 0;
}

#pragma mark ---UDP数据发送端
int sendPacket()
{
    printf("请输入接收方IP地址:\n");
    char ip[INET_ADDRSTRLEN];
    scanf("%s",ip);
    
    int send_sock;
    send_sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (send_sock < 0) return 0;
    
    struct sockaddr_in addr;
    addr.sin_len = sizeof(struct sockaddr_in);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(2001);
    inet_aton(ip, &addr.sin_addr);
    
    printf("准备完毕,可以开始通信,输入字符'C'停止发送\n");
    char send_buffer[512];
    while (1) {
        
        memset(send_buffer, 0, 512);
        printf("请输入要发送的内容:");
        gets(send_buffer);
        size_t buffer_len = strlen(send_buffer);
        if (buffer_len == 0) continue;
        
        ssize_t send_len = sendto(send_sock,
                                  &send_buffer,
                                  buffer_len,
                                  0,
                                  (struct sockaddr*)&addr,
                                  addr.sin_len);
        
        if (send_len == buffer_len) {
            printf("UDP数据包发送成功\n");
        }else {
            printf("UDP数据包发送失败: %s\n",strerror(errno));
            break;
        }
        
        if (strcmp(send_buffer, "C") == 0) break;
    }
    
    printf("关闭UDP套接字\n");
    close(send_sock);
    
    return 1;
}

#pragma mark ---UDP数据接收端
int recvPacket()
{
    int recv_sock;
    recv_sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (recv_sock < 0) return 0;
    
    struct sockaddr_in addr,recv_addr;
    socklen_t len = sizeof(struct sockaddr_in);
    
    addr.sin_family = AF_INET;
    addr.sin_len = sizeof(struct sockaddr_in);
    addr.sin_port = htons(2001);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    if (bind(recv_sock, (struct sockaddr*)&addr, addr.sin_len) < 0) {
        printf("%s\n",strerror(errno));
        return 0;
    }
    
    printf("开始接收UDP数据包,收到字符'C'停止接收\n");
    char recv_buffer[512];
    while (1) {
        memset(recv_buffer, 0, 512);
        
        ssize_t recv_len = recvfrom(recv_sock,
                                    recv_buffer,
                                    sizeof(recv_buffer),
                                    0,
                                    (struct sockaddr*)&recv_addr,
                                    &len);
        if (recv_len <= 0) {
            printf("接收UDP数据包失败:%s\n",strerror(errno));
            break;
        }
        printf("recv: %s\n",recv_buffer);
        
        if (strcmp(recv_buffer, "C") == 0) break;
    }
    printf("关闭UDP套接字\n");
    close(recv_sock);
    
    return 1;
}

五.尾声

接下来的文章会继续更新有关套接字的详解,这是我起初学习的一个流程,希望也对大家有帮助!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259