【Linux】Linux下使用套接字进行网络编程

我要成为C++领域大神 2024-07-12 08:37:03 阅读 54

🔥博客主页: 我要成为C++领域大神

🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】

❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

用于网络应用开发,使用系统提供的一套API函数接口,称为套接字函数

所有的I/O设备都被抽象为文件,一切皆文件,Everything is a File,磁盘、网络数据、终端,甚至进程间通信工具管道pipe等都被当做文件对待。

套接字文件描述符包含有IP地址和PORT端口号。

socket创建套接字

函数原型:<code>int sockfd=socket(AF_INET,SOCKET_STREAM(流式or报式),int protocol(默认流式协议TCP))

成功返回sock_fd,失败返回-1,并设置errno

bind绑定IP和端口

函数原型:bind(sock_fd,struct sockaddr *addr)

struct sockaddr *addr是早期的网络通信结构体,为了向前兼容,仍然保留。

struct sockaddr_in addr网络信息结构体

addr.sin_family=AF_INET

addr.sin_port=大端(8080)

addr.sin_addr.s_addr=大端(ip)

大小端转换常用函数

htons()小端转大端端口

htonl()小端转大端IP

ntohs()大端转小端端口

ntohl()大端转小端IP

inet_pton(AF_INET,char *ip,void *addr)

inet_ntop(AF_INET,void *addr,char *ip,16)


绑定可以对socket设置自定义的IP和端口号,其次当绑定某端口的进程退出,可以临时禁用端口号,禁用时长为2MSL。

成功返回0,失败返回-1

listen网络事件监听(TCP)

函数原型:listen(int sockfd,int backlog)

backlog:监听序列数

成功返回0,错误返回-1,并设置errno,用于错误处理

connet请求TCP连接函数(主动端)

函数原型:<code>connect(int mysockfd,struct sockaddr * dest,socklen_t addrlen)

客户端调用该函数向服务器发起TCP连接请求

成功返回0,错误返回-1,并设置errno,用于错误处理

Accept等待TCP连接请求,完成连接(被动端)

Accept为阻塞等待连接,每次只能连接一个客户端,如果要多次连接,要执行多次该函数

函数原型:

int client_fd=accpet(int server_fd,sockaddr * clientAddr(output),socklen_t *Addrlen(intput|output))

arg[1] 包含自身网络信息的socket

arg[2] 连接成功,传出保存客户端的网络信息(IP,port)

arg[3] 传入可以接收的网络信息大小,传出实际大小

成功返回请求端的socketfd,错误返回-1,并设置errno,用于错误处理

CS架构中,服务器socket只有一个,客户端socket有多个(server_fd用于连接,client_fd用于与客户端交互数据)

recv读取数据

在Linux下,read也可以读取socket数据(TCP),因为在Linux下,套接字以文件描述符的形式记录。

函数原型:ssize_t recv(int sockfd,void *buf,size_t size,int flag)

成功返回读取的数据量,失败返回-1

可以通过将flag设置为MSG_DONTWAIT实现非阻塞读取数据

send发送数据

在Linux下,write也可以发送socket数据(TCP),因为在Linux下,套接字以文件描述符的形式记录。

函数原型:ssize_t send(int sockfd,void *buf,size_t len,int flag)

面试题:

在编写一个服务端-客户端模型时,客户端异常退出,服务端也异常退出。

原因

当客户端异常退出时,客户端关闭了它的读端文件描述符。此时,如果服务端尝试向管道或套接字写数据,会触发 SIGPIPE 信号,默认行为是终止进程,因此服务端也会异常退出。

解决方案

可以在 send() 函数中使用 MSG_NOSIGNAL 标志位,来忽略 SIGPIPE 信号,从而避免服务端异常退出

send(int sockfd, buffer, len, MSG_NOSIGNAL);

使用 MSG_NOSIGNAL 标志位,写操作在对端关闭连接的情况下不会触发 SIGPIPE 信号,而是会返回 EPIPE 错误,程序可以根据这个错误码进行处理而不是直接终止。

在公网上利用TCP协议实现网络数据传输

由于服务端和客户端不在同一个局域网下,所以需要通过互联网进行通信,虚拟机要配置网络连接。

服务端:

服务端是运行在公网上的阿里云服务器。

#include <stdio.h>

#include <stdlib.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <string.h>

#define _SERVER_IP "xxx.xxx.xxx.xxx"

#define _PORT 8080

#define _BACKLOG 128

#define _SHUTDOWN 1

#define _TRUE 1

#define _FALSE 0

#define _IPSIZE 16

#define _RECVLEN 1500

int main()

{

struct sockaddr_in serverAddr,clientAddr;

int server_fd;

int client_fd;

char Result[_RECVLEN];

char client_ip[_IPSIZE];

socklen_t Addrlen;

serverAddr.sin_family=AF_INET;

serverAddr.sin_port=htons(_PORT);

serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);

server_fd=socket(AF_INET,SOCK_STREAM,0);

bind(server_fd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));

listen(server_fd,_BACKLOG);

printf("Test TCP Server Version 1.1.0 is Running...\n");

while(_SHUTDOWN)

{

Addrlen=sizeof(clientAddr);

if((client_fd=accept(server_fd,(struct sockaddr*)&clientAddr,&Addrlen))>0)

{

bzero(Result,sizeof(Result));

bzero(client_ip,sizeof(client_ip));

inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,client_ip,_IPSIZE);

printf("Connection From :IP[%s],PORT[%d]\n",client_ip,ntohs(clientAddr.sin_port));

sprintf(Result,"Hi [%s] Welcome to my TCP test server!service version 1.1.0...",client_ip);

send(client_fd,Result,strlen(Result),0);

close(client_fd);

}

else

{

perror("accpet failed");

close(server_fd);

exit(0);

}

}

close(server_fd);

return 0;

}

客户端:

客户端是运行在本地上的虚拟机镜像

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <sys/socket.h>

#include <arpa/inet.h>

//客户端源码编写,连接服务器成功,服务器反馈信息

#define _IP "xxx.xxx.xxx.xxx"

#define _PORT 8080

int main()

{

struct sockaddr_in ServerAddr;

bzero(&ServerAddr,sizeof(ServerAddr));

ServerAddr.sin_family=AF_INET;

ServerAddr.sin_port=htons(_PORT);

inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);

int Myfd=socket(AF_INET,SOCK_STREAM,0);

//看需求决定是否要绑定

char Response[1024];//存放服务端反馈信息

ssize_t recvlen;

bzero(Response,sizeof(Response));

if((connect(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)

{

if((recvlen=recv(Myfd,Response,sizeof(Response),0))>0)

{

printf("%s\n",Response);

}

}

else

{

printf("Connect failed\n");

close(Myfd);

exit(0);

}

close(Myfd);

printf("Client is Over\n");

return 0;

}

运行结果:

函数二次包裹

在我们编写代码时,通常需要根据函数的返回值来判断函数调用情况,这个过程需要大量的if else语句,减少了代码阅读的简洁性。所以为了方便我们阅读,需要对函数进行二次包裹。

在自定义的头文件中重新声明一个函数,函数名与接口函数不同,返回值、参数均与接口函数相同。在相应的源文件中定义我们的函数,将逻辑判断的过程放在函数中。这样在调用函数时就省去大量的if else判断

<code>#ifndef __MySock_H__

#define __MySock_H__

#include <sys/socket.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

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

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

ssize_t RECV(int sockfd,void *buf,size_t len,int flags);

ssize_t SEND(int sockfd,void *buf,size_t len,int flags);

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

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

int LISTEN(int sockfd,int backlog);

#endif

#include "MySock.h"

int SOCKET(int domain,int type,int protocol)

{

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

if(reval==-1)

{

perror("socket call failed");

exit(0);

}

return reval;

}

int BIND(int sockfd,struct sockaddr* addr,socklen_t addrlen)

{

int reval=bind(sockfd,addr,addrlen);

if(reval==-1)

{

perror("bind call failed");

exit(0);

}

return reval;

}

ssize_t RECV(int sockfd,void *buf,size_t len,int flags)

{

ssize_t reval;

reval=recv(sockfd,buf,len,flags);

if(reval==0)

perror("recv call failed");

return reval;

}

ssize_t SEND(int sockfd,void *buf,size_t len,int flags)

{

ssize_t reval;

reval=send(sockfd,buf,len,flags);

if(reval==-1)

perror("send call failed");

return reval;

}

int CONNECT(int sockfd,struct sockaddr *addr,socklen_t addrlen)

{

int reval=connect(sockfd,addr,addrlen);

if(reval==-1)

{

perror("connect call failed");

exit(0);

}

return reval;

}

int ACCEPT(int sockfd,struct sockaddr* addr,socklen_t *addrlen)

{

int reval=accept(sockfd,addr,addrlen);

if(reval==-1)

{

perror("accept call failed");

exit(0);

}

return reval;

}

int LISTEN(int sockfd,int backlog)

{

int reval=listen(sockfd,backlog);

if(reval==-1)

{

perror("listen call failed");

exit(0);

}

return reval;

}

使用包裹后的函数对我们TCP通信的客户端进行改写:

代码看起来简洁多了

#include "MySock.h"

//客户端源码编写,连接服务器成功,服务器反馈信息

#define _IP "xxx.xxx.xxx.xxx"

#define _PORT 8080

int main()

{

struct sockaddr_in ServerAddr;

bzero(&ServerAddr,sizeof(ServerAddr));

ServerAddr.sin_family=AF_INET;

ServerAddr.sin_port=htons(_PORT);

inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);

int Myfd=SOCKET(AF_INET,SOCK_STREAM,0);

//看需求决定是否要绑定

char Response[1024];//存放服务端反馈信息

ssize_t recvlen;

bzero(Response,sizeof(Response));

if((CONNECT(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)

{

if((recvlen=recv(Myfd,Response,sizeof(Response),0))>0)

{

printf("%s\n",Response);

}

}

close(Myfd);

printf("Client is Over\n");

return 0;

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。