目录
一丶socket
1.简介
1》1982 - Berkeley Software Distributions 操作系统引入了socket作为本地进程之间通信的接口
2》1986 - Berkeley 扩展了socket 接口,使之支持UNIX 下的TCP/IP 通信
3》现在很多应用 (FTP, Telnet) 都依赖这一接口
Socket
1、是一个编程接口,API---->函数Socket();
2、是一种特殊的文件描述符 (everything in Unix is a file)
3、并不仅限于TCP/IP协议
4、面向连接 (Transmission Control Protocol - TCP)
5、无连接 (User Datagram Protocol -UDP )
为什么需要Socket?
普通的I/O操作过程
•打开文件->读/写操作->关闭文件
•TCP/IP协议被集成到操作系统的内核中,引入了新型的“I/O”操作
•进行网络通信的两个进程在不同的机器上,如何连接?
•网络协议具有多样性,如何进行统一的操作
需要一种通用的网络编程接口:Socket
2.类型
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM) UDP
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
原始套接字(SOCK_RAW)
可以对较低层次协议如IP、ICMP直接访问。
3.端口 port
● 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区分● TCP端口号与UDP端口号独立
● 端口用两个字节来表示 2byte 16bit众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
已登记端口:1024~49151 (选1000以上10000以下)
动态或私有端口:49152~65535
套接字创建以后,套接字对应的端口号可以指定,如果不指定,则随机分配
4.字节序
发送时需要将主机字节序转换成网络字节序(大端)
1. 小端字节序:低序字节存储在低地址上;
2. 大端字节序:低序字节存储在高地址上;
注意:数据的读取顺序都是从低地址往高地址读取,通过大小端转换得到结果。
4.端口转换
主机字节序转换为网络字节序 (小端序->大端序)
u_long htonl (u_long hostlong); //host to internet long
u_short htons (u_short short); //掌握这个
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
5.IP地址转换
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
in_addr_t inet_addr(const char *cp); //从人看的到机器识别的32位无符
号整数
char * inet_ntoa(struct in_addr in); //从机器到人
二丶TCP编程
服务器-------------------------------------------------------------------》接电话者
1. 创建套接字(socket)---------------》有手机
2. 指定网络信息---------------------------》有号码
3. 绑定套接字(bind)------------------》绑定手机(插卡)
4. 监听套接字(listen)-----------------》待机
5. 接收客户端连接连接请求(accept)--》接电话
6. 接收、发送数据(recv send)---》通话
7. 关闭套接字(close)-----------------》挂电话
客户端-------------------------------------------------------------------》打电话者
1. 创建套接字(socket)------------》有手机
2. 指定(服务器)网络信息--------》有对方的号码
3. 连接(connect)-------------------》拨打电话
4. 接收发送消息(recv send)---》通话
5. 关闭套接字(close)------------》挂电话
函数接口:
1.socket
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
domain:协议族
AF_UNIX, AF_LOCAL 本地通信
AF_INET ipv4
AF_INET6 ipv6
type:套接字类型
SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
SOCK_RAW:原始套接字
protocol:协议 - 填0 自动匹配底层 ,根据type
系统默认自动帮助匹配对应协议
传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
返回值:
成功 文件描述符
失败 -1,更新errno
2.bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定
参数:
socket:套接字
addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信当时socket第一个参数确定)
addrlen:结构体大小
返回值:成功 0 失败-1,更新errno
通用结构体:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
ipv4通信结构体:
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr;
};
本地通信结构体:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* pathname */
};
3.linten
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
sockfd:套接字
backlog:同时响应客户端请求链接的最大个数,不能写0.
不同平台可同时链接的数不同,一般写6-8个
(队列1:保存正在连接)
(队列2,连接上的客户端)
返回值:成功 0 失败-1,更新errno
4.accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept(sockfd,NULL,NULL);
功能:阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
Sockfd :套接字
addr: 链接客户端的ip和端口号
如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小
如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:文件描述符; //用于通信
失败:-1,更新errno
5.recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据
参数:
sockfd: acceptfd ;
buf 存放位置
len 大小
flags 一般填0,相当于read()函数
MSG_DONTWAIT 非阻塞
返回值:
< 0 失败出错 更新errno
==0 表示客户端退出
>0 成功接收的字节个数
6.connect
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
sockfd:socket函数的返回值
addr:填充的结构体是服务器端的;
addrlen:结构体的大小
返回值
-1 失败,更新errno
正确 0
7.send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:socket函数的返回值
buf:发送内容存放的地址
len:发送内存的长度
flags:如果填0,相当于write();
服务器代码:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
char buf[128] = {0};
int ret,acceptfd;
// 1.创建套接字(socket)---------------》有手机
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd); // 3
// 2.指定网络信息---------------------------》有号码
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET; // IPV4
saddr.sin_port = htons(atoi(argv[1])); // 端口号
//saddr.sin_addr.s_addr = inet_addr("192.168.50.13"); // 虚拟机IP
//saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
saddr.sin_addr.s_addr = INADDR_ANY;
int len=sizeof(caddr);
// 3.绑定套接字(bind)------------------》绑定手机(插卡)
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err");
return -1;
}
printf("bind ok\n");
// 4.监听套接字(listen)-----------------》待机
if (listen(sockfd, 6) < 0)
{
perror("listen err");
return -1;
}
printf("listen ok\n");
// 5.接收客户端连接连接请求(accept)--》接电话
// tcp服务器一共有两类文件描述符,一类用于连接,一类用于通信
// socket函数返回值:用于连接的文件描述符
// accept函数返回值:用于通信的文件描述符
while (1)
{
acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accept err");
return -1;
}
printf("port:%d ip:%s\n",ntohs(caddr.sin_port),inet_ntoa(caddr.sin_addr));
printf("acceptfd:%d\n", acceptfd);
// 6.接收、发送数据(recv send)---》通话
while (1)
{
// read/write()
ret = recv(acceptfd, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv err");
return -1;
}
else if (ret == 0)
{
printf("client exit\n");
break;
}
else
{
printf("buf:%s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
// 7.关闭套接字(close)-----------------》挂电话
close(acceptfd);
}
close(sockfd);
return 0;
}
客户端代码
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
char buf[128] = {0};
// 1.创建套接字(socket)------------》有手机
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd);
// 2.指定(服务器)网络信息--------》有对方的号码
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
// 3.连接(connect)-------------------》拨打电话
if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("connect err");
return -1;
}
printf("connect okk\n");
// 4.接收发送消息(recv send)---》通话
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
if (!strcmp(buf, "quit"))
break;
// write(sockfd, buf, sizeof(buf));
send(sockfd, buf, sizeof(buf), 0);
}
// 5.关闭套接字(close)------------》挂电话
close(sockfd);
return 0;
}