socket编程
TCP通信:
Server: socket->bind->listen->accept->recv->send->close
Client: socket->connect->send->recv->close
TCP服务器
- 专用socket地址
TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体。
sockaddr_in:
struct sockaddr_in
{
sa_family_t sin_family; // 地址族:AF_INEt
u_init16_t sin_port; // 端口号,用网络字节序表示
struct in_addr sin_addr; // IPv4地址结构体
unsigned char sin_zero[8]; // 为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
};
struct in_addr
{
u_int32_t s_addr; // IPv4地址,用网络字节序表示
}
例:
#define PORT 8111
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(PORT);
local_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(local_addr.sin_zero), 8);
- 创建socket——socket
#include<sys/types>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
domain:告诉系统使用哪个底层协议族。
对于TCP/IP协议族,设置为PF_INET或PF_INET6;对于UNIX本地域协议族,设置为PF_UNIX。
type:指定服务类型。
对于TCP/IP协议族,取SOCK_STREAM表示传输层使用TCP协议,SOCK_DGRAM表示传输层使用UDP协议。
自Linux2.6.17起,type可以接受SOCK_STREAM或SOCK_DGRAM与下面两个标志相与的值:SOCK_NONBLOCK和SOCK_CLOEXEC。
SOCK_NONBLOCK:将新创建的socket设为非阻塞的;
SOCK_CLOEXEC:用fork调用创建子进程时在子进程中关闭该socket。
在2.6.17之前的Linux中,需要用额外的系统调用,比如fnctl来设置。
protocol:在前两个参数构成的协议集合下,再选择一个具体的协议(通常设置为0)。
返回值:
成功返回一个socket文件描述符,失败返回-1并设置errno。
例:
int socket_fd = -1;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd==-1){
perror("create socket error");
exit(1);
}
- 绑定socket地址与端口——bind
将一个socket与socket地址绑定称为给socket命名。只有命名sockethh后,客户端才知道如何连接它(客户端通常采取匿名的方式)。
#include<sys/types>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen指出该socket地址的长度。
返回值:
成功返回0,失败返回-1并设置errno。
errno:
EACCES:被绑定的地址是受保护的,仅超级用户可访问。
EADDRINUSE:被绑定的地址正在使用中。
例:
ret = bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
if(ret==-1){
perror("bind error");
exit(1);
}
- 监听socket——listen
使用系统调用来创建一个监听队列以存放待处理的客户连接:
#include<sys/socket.h>
int listen(int sockfd, int backlog);
参数:
sockfd:指定被监听的socket。
backlog:内核监听队列的最大长度。
监听队列的长度如果超过backlog,服务器将不受理新的客户连接,客户端将收到ECONNREFUSED。
例:
ret = listen(socket_fd, backlog);
if(ret==-1){
perror("listen error");
exit(1);
}
- 接受连接——accept
#include<sys/types>
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:执行过listen监听的socket。
addr:用来获取被接受连接的远端socket地址。
addrlen:远端socket地址的长度。
返回值:
accept成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器通过读写该socket来与对应的客户端通信。
accept失败时返回-1并设置errno。
socklen_t addr_len = sizeof(struct sockaddr_in);
accept_fd = accept(socket_fd, (struct sockaddr *)&remote_addr, &addr_len);
- 数据读写——recv与send
#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参数:
sockfd: 要读取的socket文件描述符
buf: 读缓冲区
len: 读缓冲区大小
flags: 见下文
send参数:
sockfd: 要写入的socket文件描述符
buf: 写缓冲区
len: 写缓冲区大小
失败返回-1
例:
char in_buf[MESSAGE_SIZE]={0};
while(1)
{
memset(in_buf, 0, MESSAGE_SIZE);
ret = recv(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
if(ret==0){
break;
}
printf("receive message:%s\n", in_buf);
send(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
}
flags参数:
- 关闭连接——close
#include<unistd.h>
int close(int fd);
参数:
fd: 待关闭的socket
close并非总是立即关闭一个连接 而是将fd的引用计数减1 当fd的引用计数为0时 才真正关闭连接
在多进程编程中 一次fork默认使父进程中打开的socket引用计数加1 因此必须在父进程和子进程中都对socket执行close调用才能将连接关闭
TCP服务器实例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8111
#define MESSAGE_SIZE 1024
int main()
{
int ret=-1;
int socket_fd = -1;
int accept_fd = -1;
int backlog = 10;
char in_buf[MESSAGE_SIZE]={0};
struct sockaddr_in local_addr, remote_addr;
// crreate a tcp socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd==-1){
perror("create socket error");
exit(1);
}
// set option of socket
ret = setsocket(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
if(ret==-1){
perror("setsockopt error");
}
// set local address
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(PORT);
local_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(local_addr.sin_zero), 8);
// bind socket
ret = bind(socket_fd, (struct sockaddr*)&local_addr, sizeof(struct sockadddr_in));
if(ret==-1){
perror("bind error");
exit(1);
}
ret = listen(socket_fd, backlog);
if(ret==-1){
perror("listen error");
exit(1);
}
// loop
for(;;){
socklen_t addr_len = sizeof(struct sockaddr_in);
// accept a new connection
accept_Fd = accept(socket_fd, (struct sockaddr *)&remote_addr, &addr_len);
for(;;){
memset(in_buf, 0, MESSAGE_SIZE);
// receive newwork data and print it
ret = recv(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
if(ret==0){
break;
}
printf("receive message:%s\n", in_buf);
send(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
}
printf("close client connection...\n");
close(accept_fd);
}
printf("quit server..\n");
close(socket_fd);
return 0;
}
TCP客户端
- 主动与服务器建立连接——connect
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
参数:
sockfd: 由socket系统调用返回一个socket
serv_addr: 服务器监听的socket地址
addrlen: 地址的长度
返回值:
成功返回0, 失败返回-1并设置errno
ECONNREFUSED: 连接被拒绝
ETIMEOUT: 连接超时
TCP客户端实例
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include<iostream>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 8111
#define MESSAGE_LENGTH 1024
int main()
{
int ret=-1;
int socket_fd;
// server addr
struct sockaddr_in serverAddr;
char sendbuf[MESSAGE_LENGTH];
char recvbuf[MESSAGE_LENGTH];
int data_len;
if((socket_fd = socket(AF_INET, SOCK_STREAM,0))<0)
{
perror("socket");
return 1;
}
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
// inet_addr()函数, 将点分十进制IP转换成网络字节序IP
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(socket_fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
{
perror("connect");
return 1;
}
printf("success to connect server...\n");
while(1)
{
memset(sendbuf, 0, MESSAGE_LENGTH);
printf("<<<<send message:");
std::cin>>sendbuf;
ret = send(socket_fd, sendbuf, strlen(sendbuf), 0);
if(ret<=0){
printf("the connection is disconnection!\n");
break;
}
if(strcmp(sendbuf, "quit") == 0){
break;
}
printf(">>> echo message:");
recvbuf[0] = '\0';
data_len = recv(socket_fd, recvbuf, MESSAGE_LENGTH, 0);
recvbuf[data_len]='\0';
printf("%s\n", recvbuf);
}
close(socket_fd);
return 0;
}