linux网络编程,其实指的就是socket编程,下面仅记下自己对socket的理解。
1. socket介绍
socket又叫套接字,在linux中,一切皆文件,socket其实也就是一种文件描述符。它定义了一组接口,
是处于应用层(比如http、telnet、ftp等)和TCP/IP协议之间的一个抽象层。
常用的socket编程一般分TCP和UDP两种:
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的,可靠的,基于字节流的传输
层的协议。UDP(User Datagram Protocol 用户数据报协议)是一种无连接的,不可靠的传输层协议。
下图要介绍的就是TCP服务器和客户端使用socket建立连接和数据交互的过程:
TCP编程分为服务端和客户端两种,服务端监听端口,被动等待客户端主动发起连接。
2. socket基本接口介绍
以下所有函数接口,都可以在linux系统下,使用命令man 函数名,查看函数要引用的头文件、参数列表、
等详细说明。
2.1 创建socket
int socket(int domain, int type, int protocol);
创建socket,其实就是创建一个文件描述符, domain是协议域,又称协议族,常用的有AF_INET、
AF_INET6等;type是socket的类型,TCP使用SOCK_STREAM,UDP使用SOCK_DGRAM;protocol是
协议,常用的有IPPROTO_TCP、IPPROTO_UDP,一般填0,会自动选择type类型对应的默认协议。
2.2 绑定ip地址和端口 bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函数主要用于服务端,参数sockfd是socket函数的返回值,addr包含了协议族、ip地址、端口。
addrlen是地址的长度。
2.3 监听本地地址和端口 listen
int listen(int sockfd, int backlog);
只有服务端会使用listen函数,第一个参数是bind之后的socket,backlog是服务器可以缓存的最大
连接个数,理解listen有一个误区,以为listen之后,就可以等待客户端的连接了,其实真正等待客户端连
接的是下面要说的accept函数,客户端发起connect之后,当同一时间有多个客户端都发起连接时,如果
服务器不能及时处理,就会将连接缓存起来,backlog就是缓存队列的最大个数。
2.4 接受客户端的连接 accept
int accept(int sockfd, struct sockaddr *client_addr, socklen_t *len);
服务器监听到客户端connect请求后,使用accept接受请求,参数sockfd是服务器绑定的socket,返回值
是一个新建的socket,用于和客户端收发数据,同时参数2,3保存了客户端的ip地址和端口。
2.5 收发数据recv和send
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);
参数sockfd是accept返回的文件描述符,recv从sockfd的缓冲区中数据复制到buf中,由于TCP是
基于字节流的,所以每次recv的数据长度和想要接收的长度可能不一致,因此接收数据时,一般都有个
循环操作,不停地接收数据,直到接收到足够的数据。而send是从buf中向发送缓冲区中写入数据,但是
可能会出现发送窗口满的情况,所以send一般也会在一个循环中。
3. 简单的TCP服务端和客户端的C++代码实现
server阻塞等待客户端的连接,接受连接成功后,循环接收客户端发来的一行消息,并打印到屏幕
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <pthread.h>
using namespace std;
//获取客户端 ip:port 格式的字符串
string getpeeraddrstr(int sockfd)
{
struct sockaddr_in addr = {0};
unsigned int size = sizeof(addr);
getpeername(sockfd, (struct sockaddr*)&addr, &size);
stringstream ssaddr;
ssaddr << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
return ssaddr.str();
}
//TCP服务端主函数
int main()
{
int opt = 1;
int svrfd = -1;
int cltfd = -1;
unsigned short svrport = 9999;
struct sockaddr_in svraddr = {0};
struct sockaddr_in cltaddr = {0};
unsigned int addrlen = sizeof(cltaddr);
//创建socket
svrfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == svrfd)
{
perror("socket failed");
return -1;
}
//设置地址重用
setsockopt(svrfd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
//绑定ip地址和端口
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(svrport); //服务端绑定端口
svraddr.sin_addr.s_addr = htonl(INADDR_ANY); //服务端绑定任意IP
if(-1 == bind(svrfd, (struct sockaddr*)&svraddr, sizeof(svraddr)))
{
perror("bind failed");
close(svrfd);
return -1;
}
//开始监听
if(-1 == listen(svrfd, 10))
{
perror("listen failed");
close(svrfd);
return -1;
}
while(1)
{
memset(&cltaddr, 0, sizeof(cltaddr));
//服务端阻塞等待客户端的连接
cltfd = accept(svrfd, (struct sockaddr*)&cltaddr, &addrlen);
if(-1 == cltfd)
{
perror("accept failed");
return -1;
}
string straddr = getaddr(cltaddr);
cout << "connect accept: " << straddr << endl;
//客户端连接成功后 服务端接收客户端发来的一行消息 并打印到屏幕
while(1)
{
char buffer[100] = {0};
int recvlen = 0;
recvlen = recv(cltfd, buffer, sizeof(buffer), 0);
if(0 < recvlen) //接收成功
{
cout << "recv from " << straddr << " " << buffer;
}
else
{
cout << "client " << straddr <<" closed" << endl;
break;
}
}
}
}
客户端主动发起连接,从命令行接收键盘输入一行信息,并发送给server
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main()
{
int sock = -1;
const char* svrip = "0.0.0.0";
unsigned short svrport = 9999;
struct sockaddr_in svraddr = {0};
//创建socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sock)
{
perror("socket failed");
return -1;
}
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(svrport);
svraddr.sin_addr.s_addr = inet_addr(svrip);
if(-1 == connect(sock, (struct sockaddr*)&svraddr, sizeof(svraddr)))
{
perror("connect failed");
close(sock);
return -1;
}
cout << "connect success" << endl;
cout << "input: ";
while(1)
{
char c = getchar();
int len = 0;
if('\n' == c)
{
cout << "input: ";
}
len = send(sock, &c, 1, 0);
if(len < 0)
{
break;
}
}
return -1;
}
4. 简单的makefile编写
将上面的服务端和客户端代码分别放在tcpserver.cpp和tcpclient.cpp文件中,并在同一目录下创建makefile文件,
执行如下命令:
make tcpserver 和 ./tcpserver 编译并运行tcpserver
make tcpclient 和 ./tcpclient 编译并运行tcpclient
makefile如下:
.PHONY: tcpserver tcpclient
tcpserver:
g++ -o tcpserver tcpserver.cpp -Wall -g -lpthread
tcpclient:
g++ -o tcpclient tcpclient.cpp -Wall -g -lpthread
clean:
rm -rf tcpserver
rm -rf tcpclient