Socket 通信

文章目录

    • Socket 通信创建流程图
    • 通信示例
    • 对一些概念进行讲述
    • 对Socke 编程所用的函数进行讲解

网络通信 和 Socket

Socket 通信_第1张图片

Socket 通信流程图 :

Socket 通信_第2张图片

通信示例 

对Socket 编程有一个初步的了解, 看看具体代码是如何实现的. 

示例的主要功能:  实现大小写的转化,客户端发送数据

服务器端进行处理,再返回给客户端.

服务器端的实现   echo_socket.c

#include
#include
#include
#include
#include
#include
#include

#define SERVER_PORT  666     //服务器的端口号

int main(void){
    
    int sock;//代表信箱
    struct sockaddr_in server_addr; //标签,保存端口号,ip地址等

    //1, 创建信箱
    sock = socket( AF_INET , SOCK_STREAM , 0);
    
    //2. 清空标签,写上地址和端口号
    bzero( &server_addr ,sizeof(server_addr));
    server_addr.sin_family = AF_INET; //选择协议族IPV4
    server_addr.sin_addr.s_addr = htonl( INADDR_ANY ); //监听本地所有ip地址
    server_addr.sin_port = htons( SERVER_PORT ); //绑定我们的端口号

    //3. 将我们的标签贴到信箱上
    bind(sock ,(struct sockaddr *)&server_addr,sizeof(server_addr));

    //4. 将我们的信箱挂到传达室,这样,保安就可以接收信件了
    listen(sock, 128);    //这里的128表示同时可以接收的信件数量

    //万事俱备,只等来信
    printf("等待客户端的来信\n");
   
    int done =1;
    //不断接受来信
    while( done ){
    	struct sockaddr_in client;
	int client_sock,len;
	char client_ip[64];
	char buff[256];

	socklen_t client_addr_len;
	client_addr_len = sizeof(client);
	client_sock = accept(sock ,(struct sockaddr *)&client, &client_addr_len);
    //打印客户端ip地址和端口号
	//inet_ntop  将网络字节序转化成字符串
	printf("client ip: %s\t port : %d\n",
	      inet_ntop( AF_INET, &client.sin_addr.s_addr,client_ip,sizeof(client_ip)), ntohs(client.sin_port));
	
	//5 、读取客户端发送的数据   read()
	len=read(client_sock , buff , sizeof(buff)-1);
	//手动添加字符串结束符
	buff[len]='\0';
	//打印读取的信息
	printf("recive :%s recive len :%d \n",buff,len);
        /* 完成大小写转换 */
	int i=0;
	for(i ; i< len ;i++)
	{
      if(buff[i]>='a' && buff[i] <= 'z'){    //小写字母
	     	buff[i]-=32;
	  }else if(buff[i]>='A' && buff[i] <= 'Z'){
	     	buff[i]+=32;
	  }
	 // buff[i]=toupper(buff[i]);
	}
	
	//6. 将读取到的信息写回去
	len = write( client_sock, buff, len ) ;
	printf("write finaled  :%s  len: %d\n", buff, len );
	//7. 关闭客户端
	close( client_sock );	
    }
    return 0;
}

客户端的实现  echo.client.c 

#include
#include
#include
#include
#include
//#include
#include 

#define SERVER_PORT 666
#define SERVER_IP "127.0.0.1"    //本地环回地址

int main(int argc , char *agrv[]){
   
   int sockfd;     //定义一个邮箱
   char *message; 
   struct sockaddr_in serveraddr;    //定义标签,包括端口号和ip
   //清空标签
   memset( &serveraddr ,0 ,sizeof(serveraddr));
   if( argc!=2 ){
       fputs("usage : echo_client message \n",stderr);
       exit(1);
   }
   message = agrv[1];
   printf("message : %s\n",message);
   
   //创建信箱
   sockfd = socket ( AF_INET ,SOCK_STREAM , 0 );
   //写上端口号和ip
   serveraddr.sin_family = AF_INET ;  //网络簇
   inet_pton( AF_INET ,SERVER_IP ,&serveraddr.sin_addr);
   serveraddr.sin_port= htons(SERVER_PORT);       //写上端口号
    
   //连接信箱
   connect( sockfd ,(struct sockaddr *)&serveraddr, sizeof(serveraddr)); 
   
   //写数据
   int len;
   char buff[64];
   write( sockfd ,message , sizeof(message)-1);
   
   len = read (sockfd , buff , sizeof(buff)-1);

    if(len >0 ){
        buff[len]='\0';
        printf("recive :%s\n",buff);
    }else{
        printf("error\n");
    }

    printf("finished. \n");

    close(sockfd);
    

    return 0;
}

结果显示:

Socket 通信_第3张图片

概念讲述 :

  • 套接字
  • 网络字节序
  • sockaddr 数据结构
  • ip地址转换函数

套接字(socket):

表示进程在网络上的一个连接点。每个套接字都有一个唯一的端口号和IP地址,用于标识该套接字所连接的远程主机和端口, 欲建立连接的两个进程各自有应该socket 来标识, 那么这两个socket 就组成一个 socke pari 就唯一标识一个连接。

网络字节序:

计算机中有两种字节序

大端字节序   - > 低地址高字节, 高地址低字节

小端字节序   - > 低地址低字节, 高地址高字节

Socket 通信_第4张图片

内存中的多字节数据相对于内存地址有大端和小端之分 , 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。

网络数据流同样有大端小端之分。

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

 如何实现网络网络字节序和主机字节序的转换。

//包含头文件

#include

uint32_t htonl ( uint32_t  hostlong ) ;

uint16_t htons ( uint16_t  hostlong) ;

uint32_t ntopl ( uint32_t  hostlong ) ;

uint16_t  ntohs ( uint16_t  hostlong ) ;

/*

1、h  - >  host  (主机)

2、n  - >  network ( 网络 )

3 、l   - >  32位长整型

4、 s  - >  16位短整形 

5、to  - > 转换的意思可以理解成

*/

sockaddr 数据结构:

Socket 通信_第5张图片

内部结构:

struct sockeaddr{
    sa_family_t  sa_family ;    //网络地址族, 如ipv4 (AF_INET)
    char sa_data[14];           //14字节的地址数据
}

struct sockeaddr_in{
    sa_family_t   sin_family;
    in_port_t   sin_port;       //端口号
    struct  in_addr_sin_addr;   //网络地址 
}


struct  in_addr_sin_addr{
    uint32_t  s_addr;          //网络字节序地址
}

ip地址转换函数:

//头文件

#include

// //网络协议地址(如IPv4或IPv6地址)转换为二进制格式

int inet_pton ( int  af , const char  * , char  *src , void *dst ) ;     

//将一个二进制格式的网络协议地址(如IPv4或IPv6地址)转换为字符串形式

const char * inet_ntop ( int af ,const void  *src ,char *dst ,socklen_t   size );

Socket 编程函数

socket() 函数  :

打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,这里就是相当于创建一个套接字 , 如果失败则返回  -1 。

#include < sys/types.h >       // 需要的头文件

#include < sys/socket.h >

int  socket ( int  domain ,int  type ,int  protocol  )  ;

// 参数说明

domain  :   地址族

这个参数用于指定套接字所使用的地址族(Address Family),它决定了套接字通信所使用的协议和网络地址的类型。

常用的地址族有AF_INET(IPv4网络)、AF_INET6(IPv6网络)等。在大多数情况下,我们使用AF_INET来创建套接字。  

type  :    套接字类型

这个参数用于指定套接字的类型,即数据传输的方式。常用的套接字类型有SOCK_STREAM(流套接字)和SOCK_DGRAM(数据报套接字)。

SOCK_STREAM 表示可靠的、面向连接的通信,通常使用TCP协议;而SOCK_DGRAM表示不可靠的、无连接的通信,通常使用UDP协议。根据实际需求选择合适的套接字类型。

protocol   :     协议      ( 默认0 ,一般情况不需要我们填写 )

这个参数用于指定套接字所使用的具体协议。

一般情况下,我们可以将这个参数设置为0,表示使用默认协议。

对于SOCK_STREAM类型的套接字,默认协议是TCP;对于SOCK_DGRAM类型的套接字,默认协议是UDP。如果需要使用其他特定的协议,可以根据协议类型设置这个参数。

bind( )   函数

将套接字绑定到指定的地址和端口。

#include < sys/types.h >

#include < sys/socket.h >

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

//  参数说明

sockfd   :   套接字 ,  由socket()函数返回的套接字描述符。

addr  :    指向套接字地址结构的指针,它包含了要绑定的IP地址和端口号。

               对于IPv4,这个地址结构通常是struct sockaddr_in类型。

  

addrlen  :   地址结构的长度    sizeof()  求出 

bind()  方法会在指定的地址和端口上绑定套接字。

如果绑定成功,返回0;如果绑定失败,返回-1,并设置相应的错误码。

  • 在调用bind()方法之前,必须确保已经使用socket()函数成功创建了套接字。
  • 绑定的IP地址和端口号应该有效且可用,避免和其他进程冲突。
  • 如果绑定的地址和端口已经被占用,将会导致绑定失败。

listen( )  函数 : 

用于将套接字转换为被动套接字,使其能够接受其他主机的连接请求。

它通常用于服务器端,使服务器能够监听并接受客户端的连接。

#include < sys/types .h >

#include < sys/socket.h >

int  listen ( int  sockfd  , int  backlog )  ;

//  参数说明 

sockfd   :     套接字

backlog  :  这个参数指定了同时等待处理的连接请求的最大数量。

                  当多个客户端同时向服务器发起连接请求时,如果连接请求的数量超过了                                backlog 指定的值,多余的连接请求可能会被拒绝。   

如果listen()函数调用成功,返回0;如果调用失败,返回-1。

使用listen()函数后,服务器就进入了“监听”状态,等待客户端的连接请求。

accept (  )  函数  :

用于接受从客户端发起的连接请求。

一旦有连接请求到达,accept() 函数会创建一个新的套接字,用于与客户端进行通信,并返回这个新套接字的描述符。

#include < sys/types.h >

#include < sys/socket.h >

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

// 参数说明

sockefd   :   套接字

addr   :      指向套接字地址结构的指针,它包含了要绑定的IP地址和端口号。

                 对于IPv4,这个地址结构通常是struct sockaddr_in类型。

                 如果不关心客户端的地址信息,这个参数可以设置为NULL

addrlen  :  这是一个指向socklen_t类型的指针,用于存储addr结构体的长度。

  

如果连接成功建立,则返回新创建的套接字的描述符。

如果发生错误,则返回-1,并设置相应的错误码。

connect  (  )  函数  : 

客户端向服务器发起连接请求。

#include < sys/types.h >

#include < sys/socket .h >

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

// 参数说明

sockfd  :   套接字

addr    :     指向套接字地址结构的指针,它包含了要绑定的IP地址和端口号。

addrlen  :   这是一个指向socklen_t类型的指针,用于存储addr结构体的长度。

当调用connect()函数时,它会尝试与指定的服务器建立连接。

如果连接成功建立,函数返回0;

如果连接失败,返回-1,并设置相应的错误码。

出错处理函数 : 

perror ( )函数 :

用于输出上一个系统调用失败时的错误描述信息。

#include < stdio.h >

#include < errno.h >

void  perror ( const  char  *s)  ;

// 例如

perror ( " connect failed " );  

法二  :  使用 fprintf ()  

它通常用于输出程序运行过程中的错误信息。与stdout(标准输出)不同,stderr的输出通常会被送到终端窗口。

fprintf( stderr, "error message :%s \n", strerror(errno) )


// strerror ()   获取错误描述

你可能感兴趣的:(linux,服务器,网络,c语言)