Linux 套接字通信笔记(一)

时间:2021-10-17 10:18:53
  • 协议

    TCP(传输控制协议),UDP(用户数据包协议)为传输层重要的两大协议,向上为HTTP提供底层协议,向下为数据链路层封装底层接口,乃是通信重中之重。TCP是面向流传输的协议,在编程中形象化为Stream,如流水一般,读入读出。流的基本单位为byte。而UDP则为数据包协议,以数据包为单位。协议的细节不再赘述,本次提供两种协议的最基础套接字编程模型。

  • API

    服务端最基本的流程:新建套接字->绑定端口->开始监听....->建立连接->传输数据->关闭连接

    客户端最基本的流程:新建套接字->连接...->传输数据->关闭连接

#服务端
#这是新建套接字的API 第一个参数ARPA Internet地址格式,第二个参数是使用流协议,第三个参数是指定协议,并未遇到应用场景 通常 传0
int server_socketfd = socket(PF_INET,SOCK_STREAM,0)
#这是绑定函数 绑定已申请的socket描述符到端口上,第二个参数是ip地址结构体指针,第三个是地址描述结构体的长度
bind(server_socketfd,(
struct sockaddr *)&my_addr,sizeof(struct sockaddr)
#接受连接函数 意味开始监听 第一个参数是服务器套接字 第二个参数返回的是客户端连接的地址 第三个是地址长度
accept(server_socketfd, (
struct sockaddr *)&remote_addr,&sin_size)

#客户端
#客户端连接函数 第一个参数是socket对象描述符,第二个是ip地址,第三个是ip地址的长度
connect(client_fd,(
struct sockaddr *)&remote_addr, sizeof(struct sockaddr))

#io
#发送函数 第一个是发送的套接字描述符 第二个是内容 第三个是发送长度 第四个是flags 一般0
send(client_socketfd,
"welcome to login\n",21,0)
#接收函数 接受client对象的数据到buf中,一次接受bufferSize个长度,最后一个参数是flags 和send对应
recv(client_socketfd, buf, bufferSize, 0)
  • 完整程序

    为了方便测试,我使用了C++和Java做一对交互程序,C++做服务器的时候就用Java做客户端,反之也有。

    Linux socket API存在头文件sys/socket.h中,并且期间使用多个头文件的函数,大概使用的头文件如下。

#include <stdio.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<unistd.h>
#include
<memory.h>

    TCP服务端Demo

#ifndef COMMUNICAITON_TCPSOCKET_H
#define COMMUNICAITON_TCPSOCKET_H
#include
<stdio.h>
#include
<memory.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<unistd.h>

#endif //COMMUNICAITON_TCPSOCKET_H
void tcpServerStart();

#include
"TCPSocket.h"
const int bufferSize = 1024;

void tcpServerStart(){
printf(
"server socket start init...\n");
int server_socketfd;//服务器套接字
int client_socketfd;//客户端套接字
int port = 8000;
struct sockaddr_in my_addr;//服务器网络地址结构
struct sockaddr_in remote_addr;//虚拟网络地址结构
socklen_t sin_size;//此处须运用长度定义类型 socketlen_t
char buf[bufferSize];//数据缓冲区
long sendLen = 0;//发送接收长度
memset(&my_addr,0,sizeof(my_addr));//数据初始化
my_addr.sin_family=AF_INET;//设置为IP通信
my_addr.sin_addr.s_addr=INADDR_ANY;//服务器ip
my_addr.sin_port=htons(port);//设置端口为8000

/*创建服务器端套接字--IPv4协议,TCP协议*/
if((server_socketfd = socket(PF_INET,SOCK_STREAM,0))<0){
perror(
"socket init error");
return ;
}
/*将套接字绑定到服务器的网络地址上*/
if(bind(server_socketfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0){
perror(
"bind socket error");
return;
}
printf(
"server socket start listen ,port:%d",port);
/*监听连接请求--监听队列长度为5*/
listen(server_socketfd,
5);

sin_size
= sizeof(struct sockaddr_in);

/*等待客户端连接请求到达*/
client_socketfd
=accept(server_socketfd, (struct sockaddr *)&remote_addr,&sin_size);
if(client_socketfd < 0){
perror(
"listen error");
return;
}
printf(
"accept %s",inet_ntoa(remote_addr.sin_addr));//打印客户端ip地址


sendLen
=send(client_socketfd,"welcome to login\n",21,0);//发送欢迎信息
if(sendLen <= 0){
perror(
"no send");
return;
}
while(sendLen=recv(client_socketfd, buf, bufferSize, 0)>0){
printf(
"accept msg===");
printf(
"%s/n",buf);
}
close(client_socketfd);
close(server_socketfd);


}

    UDP服务端Demo

#ifndef COMMUNICAITON_UDPSOCKET_H
#define COMMUNICAITON_UDPSOCKET_H

#include
<stdio.h>
#include
<memory.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<unistd.h>
#endif //COMMUNICAITON_UDPSOCKET_H

void udpServerStart();

#include "UDPSocket.h"

const int bufSize = 1024;
void udpServerStart(){
printf(
"udp server init...\n");
int server_sockfd;
int len;
int port = 8000;
struct sockaddr_in my_addr;
struct sockaddr_in remote_addr;
socklen_t sin_size;
//此处使用其自定义的socklen属性
char buf[bufSize];
memset(
&my_addr,0,sizeof(my_addr)); //数据初始化--清零
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr
= INADDR_ANY;
my_addr.sin_port
= htons(port);

/*创建服务器端套接字--IPv4协议,面向无连接通信,UDP协议*/
if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0){
perror(
"create socket error");
return;
}
/*将套接字绑定到服务器的网络地址上*/
if(bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0){
perror(
"bind socket error");
return;
}
sin_size
= sizeof(struct sockaddr_in);
printf(
"waiting for package");
/*接收客户端的数据并将其发送给客户端--recvfrom是无连接的*/
if((len=recvfrom(server_sockfd,buf,bufSize,0,(struct sockaddr *)&remote_addr,&sin_size))<0){
perror(
"recv error");
return;
}
printf(
"received packet from %s:\n",inet_ntoa(remote_addr.sin_addr));
printf(
"contents: %s\n",buf);
close(server_sockfd);
}

    其中有某些函数调用非socket API中,比如memset,inet_ntoa等。

    TCP客户端 Demo

#ifndef COMMUNICAITON_TCPCLIENT_H
#define COMMUNICAITON_TCPCLIENT_H
#include
<stdio.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<unistd.h>
#include
<memory.h>
#endif //COMMUNICAITON_TCPCLIENT_H
void tcpClient();


#include
"TCPClient.h"
const int bufSize = 1024;
void tcpClient(){
int client_fd;
int len;
struct sockaddr_in remote_addr;
char buf[bufSize];
memset(
&remote_addr,0, sizeof(remote_addr));
remote_addr.sin_family
=AF_INET;
remote_addr.sin_addr.s_addr
=inet_addr("127.0.0.1");
remote_addr.sin_port
=htons(8000);

/*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
if((client_fd=socket(PF_INET,SOCK_STREAM,0))<0){
perror(
"socket error");
return;
}
/*将套接字绑定到服务器的网络地址上*/
if(connect(client_fd,(struct sockaddr *)&remote_addr, sizeof(struct sockaddr))<0){
perror(
"connect error");
return;
}
printf(
"connected to server\n");
len
=recv(client_fd,buf,bufSize,0);
printf(
"get msg :%s",buf);
close(client_fd);

}

    UDP客户端 Demo

#ifndef COMMUNICAITON_UDPCLIENT_H
#define COMMUNICAITON_UDPCLIENT_H
#include
<stdio.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<unistd.h>
#include
<memory.h>
#endif //COMMUNICAITON_UDPCLIENT_H

void udpClient();

/*
* 向本地(localhost) 8000端口发送一个数据包
*/
const int buffSize = 1024;
void udpClient(){
int client_sockfd;
int len ;
struct sockaddr_in remote_addr;
int sin_size;
char buf[buffSize];
memset(
&remote_addr,0, sizeof(remote_addr));
remote_addr.sin_family
=AF_INET;
remote_addr.sin_addr.s_addr
=inet_addr("127.0.0.1");
remote_addr.sin_port
=htons(8000);

/*创建客户端套接字--IPv4协议,面向无连接通信,UDP协议*/
if((client_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0)
{
perror(
"socket not found");
return;
}
stpcpy(buf,
"this is a C++ udp client");
printf(
"send msg :%s\n",buf);

sin_size
= sizeof(struct sockaddr_in);
/*向服务器发送数据包*/
if((len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0){
perror(
"send error");
return;
}
close(client_sockfd);
}

    用于对应的Java客户端/服务端(比较熟悉Java,用于测试)

public class TCPClient {
public void tcpConnectReacServerFirst(String addr,int port){
Socket socket
= null;
try{
socket
= new Socket(addr,port);
InputStream ins
= socket.getInputStream();
OutputStream ous
= socket.getOutputStream();
BufferedReader bufReader
= new BufferedReader(new InputStreamReader(ins));
String msg
= bufReader.readLine();
System.out.println(
"接到服务器信息:"+msg);
ous.write(
"hello server\n".getBytes());
ous.flush();

ins.close();
ous.close();
}
catch (Exception e){
e.printStackTrace();
}
finally {
if(socket != null){
if(socket.isClosed()){
try{
socket.close();
}
catch (Exception e){
e.printStackTrace();;
}
}
}
}
}

}
public class UDPClient {
public void udpSender(String addr,int port){
byte[] msg = "this is udp sender\n".getBytes();
try{
//数据包
//数据包byte数组 数组长度 包目的ip地址 端口
DatagramPacket datagramPacket = new DatagramPacket(msg,msg.length,InetAddress.getByName(addr),port);
DatagramSocket socket
= new DatagramSocket();
socket.send(datagramPacket);
if(!socket.isClosed()){
socket.close();
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
public class TCPServer {
public void tcpServerStart(int port){
ServerSocket server
= null;
final String welcome = "welcome to login,Java TCP server!\n";
try{
server
= new ServerSocket(port);
Socket socket
= server.accept();
OutputStream ous
= socket.getOutputStream();
ous.write(welcome.getBytes());
ous.close();
socket.close();
server.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
public class UDPServer {
public void udpStartup(int port,int byteCount){
System.out.println(
"udp server start ,port:"+port);
try{
InetAddress inet
= InetAddress.getLoopbackAddress();
DatagramSocket udp
= new DatagramSocket(port);
byte[] buf = new byte[byteCount];
DatagramPacket packet
= new DatagramPacket(buf,buf.length);

udp.receive(packet);

String getMsg
= new String(buf,0,packet.getLength());

System.out.println(
"收到客户端 "+packet.getAddress().getHostAddress()+" 发来信息:"+getMsg);

udp.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
  • 体会

    C++在unix中形成了一种对对象的描述,或为句柄、或为fd,将其理解为Java中的socket对象类比一下,就容易理解了。而传入地址以及长度,应该是操作系统需要新建空间申请内存而用。总而言之,在传统通信程序中,socket的模型就是连接对象,其最大的应用就是如下

while(true){
socket
= accept();
Thread handler
= new Handler(socket);
handler.start();
}

    为此基础上优化,就是添加线程池,或者加请求队列。这种模式逻辑明了,编码简单,是最容易理解的编程模型。