文章目录
-
- Socket 通信创建流程图
- 通信示例
- 对一些概念进行讲述
- 对Socke 编程所用的函数进行讲解
网络通信 和 Socket
Socket 通信流程图 :
通信示例
对Socket 编程有一个初步的了解, 看看具体代码是如何实现的.
示例的主要功能: 实现大小写的转化,客户端发送数据
服务器端进行处理,再返回给客户端.
服务器端的实现 echo_socket.c
cpp
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<ctype.h>
#include<arpa/inet.h>
#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
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
//#include<netinet/in.h>
#include <arpa/inet.h>
#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;
}
结果显示:
概念讲述 :
- 套接字
- 网络字节序
- sockaddr 数据结构
- ip地址转换函数
套接字(socket):
表示进程在网络上的一个连接点。每个套接字都有一个唯一的端口号和IP地址,用于标识该套接字所连接的远程主机和端口, 欲建立连接的两个进程各自有应该socket 来标识, 那么这两个socket 就组成一个 socke pari 就唯一标识一个连接。
网络字节序:
计算机中有两种字节序
大端字节序 - > 低地址高字节, 高地址低字节
小端字节序 - > 低地址低字节, 高地址高字节
内存中的多字节数据相对于内存地址有大端和小端之分 , 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。
网络数据流同样有大端小端之分。
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
如何实现网络网络字节序和主机字节序的转换。
//包含头文件
#include <arpa/inet.h>
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 数据结构:
内部结构:
cpp
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 <arpa/inet.h>
// //网络协议地址(如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的输出通常会被送到终端窗口。
cpp
fprintf( stderr, "error message :%s \n", strerror(errno) )
// strerror () 获取错误描述