Contents
1 一个简单的 Socket 程序
server.cpp
#include <netinet/in.h>
#include <memory.h>
#include <unistd.h>
#include <iostream>
int main()
{
int nListenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
std::cout << nListenSocket << std::endl;
if(-1 == nListenSocket)
{
std::cout << "socket error" << std::endl;
return 0;
}
sockaddr_in ServerAddress;
memset(&ServerAddress, 0, sizeof(sockaddr_in));
ServerAddress.sin_family = AF_INET;
ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY);
ServerAddress.sin_port = htons(4000);
if(::bind(nListenSocket, (sockaddr *)&ServerAddress, sizeof(sockaddr_in)) == -1)
{
std::cout << "bind error" << std::endl;
::close(nListenSocket);
return 0;
}
if(::listen(nListenSocket, 23) == -1)
{
std::cout << "listen error" << std::endl;
::close(nListenSocket);
return 0;
}
sockaddr_in ClientAddress;
socklen_t LengthOfClientAddress = sizeof(sockaddr_in);
int nConnectedSocket = ::accept(nListenSocket, (sockaddr *)&ClientAddress, &LengthOfClientAddress);
if(-1 == nConnectedSocket)
{
std::cout << "accept error" << std::endl;
::close(nListenSocket);
return 0;
}
::write(nConnectedSocket, "Hello World\n", 13);
::close(nConnectedSocket);
::close(nListenSocket);
return 0;
}
client.cpp
#include <sys/socket.h>
#include <netinet/in.h>
#include <memory.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
int main()
{
int nClientSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if(-1 == nClientSocket)
{
std::cout << "socket error" << std::endl;
return 0;
}
sockaddr_in ServerAddress;
memset(&ServerAddress, 0, sizeof(sockaddr_in));
ServerAddress.sin_family = AF_INET;
if(::inet_pton(AF_INET, "127.0.0.1", &ServerAddress.sin_addr) != 1)
{
std::cout << "inet_pton error" << std::endl;
return 0;
}
ServerAddress.sin_port = htons(4000);
if(::connect(nClientSocket, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) == -1)
{
std::cout << "connect error" << std::endl;
return 0;
}
char buf[13];
::read(nClientSocket, buf, 13);
std::cout << buf << std::endl;
::close(nClientSocket);
return 0;
}
编译并运行
服务器端:
g++ -o server server.cpp -g
./server
3
客户端端:
g++ -o client client.cpp -g
./client
Hello World
可以看到客户端成功与服务器建立连接,服务器给客户端发送了“Hello World”。
2 Socket 函数的相关内容
2.1 Socket 函数
Socket 函数的头文件为:<sys/socket.h>
Socket 函数的原型为:int socket(int family, int type, int protocol);
Socket 函数的返回值称为套接字,这个套接字是一个非负整数值。若 Socket 函数出错,将返回-1。
其中 family 表示协议族或者协议域。有值:
协议名称 | 协议名称 | 值 |
---|---|---|
AF_INET | IPv4 协议 | 2 |
AF_INET6 | IPv6 协议 | 10 |
AF_LOCAL | Unix 域协议 | 1 |
AF_ROUTE | 路由器套接口 | 16 |
AF_KEY | 密钥套接口 | 15 |
其中 type 表示套接口的类型。有值:
接口类型 | 接口名称 | 值 |
---|---|---|
SOCK_STREAM | 字节流套接口 | 1 |
SOCK_DGRAM | 数据报套接口 | 2 |
SOCK_SEQPACKET | 有序分组套接口 | 5 |
SOCK_RAW | 原始套接口 | 3 |
SOCK_PACKET | 数据链路套接口 | 10 |
其中 protocol 是传输协议的类型。有值:
传输协议的类型 | 传输协议类型名称 | 值 |
---|---|---|
IPPROTO_TCP | TCP 传输协议 | 6 |
IPPROTO_UDP | UDP 传输协议 | 7 |
IPPROTO_SCTP | SCTP 传输协议 | 132 |
在 protocol 中,可以取值为 0,让系统根据 family 和 type 的值自动选取合适的 protocol 值。
此行代码表明 socket 连接使用的是 Ipv4 协议,套接口的类型为字节流套接口。
2.2 套接字的地址结构
Ipv4 套接口地址结构为:
在编程中,首先定义一个套接口接口对象,然后使用 memet 函数将内存区域清 0。之后对其结构体字段进行赋值。
服务端代码:
客户端代码:
2.3 bind 函数
bind 函数用于将套接口地址信息与套接口绑定在一起。
函数原型为:int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
其中参数的含义为:
参数 | 参数含义 |
---|---|
sockfd | socket 函数的返回值 |
myaddr | 套接口地址信息,包括 family、端口、IP |
addrlen | 套接口地址结构大小 |
类型 socklen_t | unsigned int |
返回值 | 成功为 0,出错为-1 |
TCP 服务器若使用 bind 函数,则将服务器绑定在一个已经设置好的端口上。如果不是用 bind 函数,则将在调用 listen 函数/connect 函数中,自动给服务器分配一个端口。
2.4 listen 函数
listen 函数将一个未连接的套接口转换为一个被动套接口。
其函数原型为:int listen(int sockfd, int backlog); 第一个 sockfd 参数表示成功创建的 TCP 套接字,第二个 backlog 参数表示 TCP 处理连接的队列长度。
listen 函数在运行中要维护两个队列:未完成连接队列和已完成连接队列。
2.5 accept 函数
accept 函数将从已完成连接的队列中取出下一个已完成的连接。如果已完成连接的队列为空,则将进入睡眠状态。
其函数原型为:int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); 若接收错误,则返回-1。
其中参数的含义为:
参数 | 参数的含义 |
---|---|
sockfd | socket 函数的返回值 |
cliaddr | 当 accept 函数返回时,内核将从已完成连接队列中,取出一个连接;并将该连接的客户端信息(协议族、IP、Port),保存在 cliaddr 指向的结构体中 |
addrlen | 调用 accept 前,*addrlen 的值为 cliaddr 实际所指的套接口地址结构的长度;accept 返回时,内核填充*addrlen,该值为确切地客户套接口地址结构长度 |
3.6 connect 函数
connect 函数的作用是使客户端向服务器端发起 TCP 连接。
其函数原型为:int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); 连接成功则返回 0,连接出错则返回-1。
其中参数的含义为:
参数 | 参数的含义 |
---|---|
sockfd | socket 函数的返回值 |
servaddr | 服务器的地址信息,包括 IP,Port,协议族 |
addrlen | 服务器套接口地址结构的长度 |
如果连接失败,则套接字将不可以再使用,必须关闭。
3.7 数据收发函数
数据收发函数有 read、write、send、recv、close。read 函数将缓冲区的数据读出,write 函数将数据发送到缓冲区。send 函数将数据发出,recv 函数接收数据。close 函数负责结束数据传输。