目录
socket
Linux哲学是:
一切皆文件
。socket也不例外,它是一个可读、可写、可控制、可关闭的
文件描述符
。
至于为什么叫套接字?
可以把通信双方发送的信息想象在水管中流通,套接字就是那个水管接头。
创建socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type,int protocol); //成功返回文件描述符,失败返回-1
domain
参数告诉系统使用哪个
底层协议族
。IPv4设置为
AF_INET
,当然使用
PF_INET
也是一样的,这两个值一样。
type
参数指定
服务类型
。对于TCP,一般使用
SOCK_STREAM
(字节流),对于UDP,一般使用
SOCK_UGRAM
(数据包)服务。同时!自Linux2.6.17版本之后,这个可以接受与下面两个重要的标志相与的值:
SOCK_NONBLOCK
(非阻塞)和
SOCK_CLOEXEC
(在子进程关闭socket)。
protocol
参数表示你想要接收什么样的数据包就把protocol设置成什么,一般设置成0,指代
IPPOROTO_IP
,
详情见此博客
。
命名socket
创建socket的时候,我们只是给他指定了地址族,但是并没有将socket与一个地址绑定起来。而绑定的过程就是命名。
在服务器程序中,我们通常要命名socket,只有命名后客户端才知道如何连接它。而客户端一般不用命名,而是使用操作系统自动分配的socket地址。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* myaddr,socklen_t addrlen); //成功返回0,失败返回-1
my_addr
所指的socket分配将分配给未命名的sockfd文件描述符
addrlen
参数指出该socket地址的长度
监听socket
socket被命名后,客户端还是不能连接,还需要使用一个系统调用来创建一个监听队列来存放待处理的客户连接。
#include <sys/socket.h>
int listen(int sockfd, int backlog); //成功返回0,失败返回-1并设置errno
- backlog参数 指定内核监听队列的最大长度,监听队列的长度如果超过backlog,服务器将不受理新的客户连接。一般使用
5
。
接受连接
下面的系统调用可以从
listen
监听队列中接受一个连接:
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen); //成功返回一个新的连接socket,失败返回-1;
发起连接
如果说服务器通过
listen
调用来被动接受连接,那么客户端则需要使用
connect
来主动发起连接:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr* serv_addr,socklen_t addrlen); //成功返回0,失败返回-1;
关闭连接
#include <unistd.h>
int close(int fd);
close并非是关闭文件,而是将引用计数-1。只有当
引用计数为0时,才真正关闭连接。如果想直接关闭连接需要用下面的
shutdown
系统调用:#include <sys/socket.h> int shutdown(int socket, int howto)
howto
参数有以下可选值:
数据读写
按照一切皆文件的哲学来说,
write
和
read
同样适用于socket。但是socket编程专门提供了几个接口来适用于socket。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t len.,int flags);
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
recv
成功时返回实际读取到的数据的长度,它可能小于我们期望的长度
len
。失败返回-1
flags
参数为数据收发提供了额外的控制:
UDP数据读写
socket中用于UDP读写的系统调用如下:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvform(int sockfd, void* buf, size_t len, int flags, struct sockaddr* scr_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr& dest_addr, socklen_t len);
通用数据读写函数
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg,int flags);
结构体
msghdr
内容如下:
一个简单的服务器与客户端通信例子
头文件:
#include<iostream>
#include<assert.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#define BUFF_SIZE 128
using namespace std;
class Socket
{
public:
Socket()
{
sockfd_ = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd_ >= 0);
}
int Get_socket()
{
return sockfd_;
}
~Socket()
{
close(sockfd_);
}
protected:
int sockfd_;
};
class Socket_Ser :public Socket
{
public:
Socket_Ser(const char* ip, int port = 6000, int backlog = 5)
{
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockfd_, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(sockfd_, backlog);
assert(ret != -1);
}
int Accept()
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connfd = accept(sockfd_, (struct sockaddr*)&client, &len);
return connfd;
}
int Recv(int fd, char* buffer, int size)
{
int ret = recv(fd, buffer, size - 1, 0);
if (ret == -1 || (strncmp(buffer, "end", 3) == 0))
{
return -1;
}
}
void Send(int fd, const char* buffer, int size)
{
send(fd, buffer, size, 0);
}
};
class Socket_Cli :public Socket
{
public:
Socket_Cli(const char* ip, int port = 6000)
{
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = inet_addr(ip);
int ret = connect(sockfd_, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
}
int Send(char* buffer, int size)
{
send(sockfd_, buffer, size, 0);
}
};
Service端:
#include "tcp_socket.h"
void Recv_Send(int connfd_)
{
while (1)
{
char buffer[BUFF_SIZE];
memset(buffer, '\0', BUFF_SIZE);
int ret = recv(connfd_, buffer, BUFF_SIZE - 1, 0);
if (strcmp(buffer, "end") == 0) return;
cout << "recv form " << connfd_ << ":" << buffer;
send(connfd_, "ok", 3, 0);
}
}
int main(int argc, char* argv[])
{
if (argc <= 2)
{
cout << "error! please input again" << endl;
return 1;
}
Socket_Ser ser(argv[1], atoi(argv[2]));
while (1) //处理多个连接
{
int connfd = ser.Accept();
if (connfd < 0)
{
cout << "accept errno!" << endl;
}
else
{
Recv_Send(connfd);
}
}
return 0;
}
Client端:
#include "tcp_socket.h"
int main(int argc, char* argv[])
{
if (argc <= 1)
{
cout << "error! please input again" << endl;
return 1;
}
Socket_Cli cli(argv[1]);
while (1)
{
char buffer[BUFF_SIZE];
memset(buffer, '\0', BUFF_SIZE);
cout << "please input:";
cin >> buffer;
cli.Send(buffer, sizeof(buffer));
if (strncmp(buffer, "end", 3) == 0) break;
}
}
参考文献
[1]游双.Linux高性能服务器编程.机械工业出版社,2043.5.