前面说的管道、消息队列、共享内存、信号、信号量,都是基于Linxux内核,无法实现多机通讯。
而网络编程中的Socket正好可以满足。现在网络上各种各样的服务大多是基于Socket来完成通信的。
网络编程四个要点:客户端的地址、客户端的端口、服务器的地址、服务器端口。
一.端口号的作用
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等
这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。
实际上是通过“IP地址+端口号”来区 分不同的服务的。
端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。
二.TCP协议与UDP协议的区别(jizhu)
1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5. TCP首部开销20字节;UDP的首部开销小,只有8个字节
6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
三、字节序
小端字节序:将低序字节存储在起始地址。
大端字节序:将高序字节存储在起始地址。
网络字节序=大端字节序
例子:在内存中双字0x01020304的存储方式
地址 4000 4001 4002 4003
小端 04 03 02 01
大端 01 02 03 04
四、Socket服务器和客户端的开发步骤
五、API介绍
1.socket函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数
1.domain:指明所使用的协议族。通常为AF_INET,表示互联网协议族(TCP/IP协议族);AF_INET:IPv4因特网域
AF_INET6:IPv6因特网域
AF_UNIX:Unix域
AF_ROUTE:路由套接字
AF_KEY:密钥套接字
AF_UNSPEC:未指定
2.type参数指定socket的类型:SOCK_STREAM:流式套接字提供可靠的、面向连接的通信流,它使用TCP协议,从而保证了数据传输的正确性和顺序性。
SOCK_DGRAM:数据报文式套接字定义了一种无连接的,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
SOCK_RAM:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
3.protocol 通常赋值0。0选择type类型对应的默认协议
IPPROTO_TCP:TCP传输协议
IPPROTO_UDP:UDP传输协议
IPPROTO_STCP:STCP传输协议
IPPROTO_TIPC:TIPC传输协议
返回
成功的话返回Scoket描述符Sockfd,失败则返回-1。
2.bind函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:用于绑定IP地址和端口号到sockfd
参数
sockfd:Socket描述符
addr:指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族不同而不同。
addrlen:sockaddr结构体类型的长度大小
返回
成功返回0,失败返回-1,并设置errno//ipv4对应的是:
struct sockaddr {
sa_family_t sa_family;//协议族 char
sa_data[14];//IP+端口
}
同等替换:/*进入/usr/include/目录,命令行grep "struct sockaddr_in {" * -nir 查找,*代表当前目录,n是显示行号,i是不区分大小写,r代表递归。查找到内容在linux/in.h +184,+184代表184行,用vi进入查看*/
struct sockaddr_in
{
sa_family_t sa_family;//协议族
in_port_t sin_port;//端口
struct in_addr sin_addr;//IP地址结构体
unsigned char sin_zero[8]
//填充,没有实际意义,只是为跟sockaddr结构在内存中对齐,这样两者才能相互转换
}
地址转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//把字符串形式的”192.168.0.123”转为网络能识别的格式,即in_addr结构体类型 int inet_aton(const char *cp, struct in_addr *inp);
//把网络格式的ip地址转为字符串形式
char *inet_ntoa(struct in_addr in);
3.监听函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:设置能处理的最大连接数,listen函数只能用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动要求与某个进程连接,只能监听是否有其他客户端进程与之连接,然后响应其连接请求,并对它做出处理。
参数
sockfd:socket描述符
backlog:在请求队列中允许的最大请求数。
4.连接函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:函数由tcp服务器调用,用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。
参数
sockfd:socket描述符
addr:用于返回已连接的客户端的协议地址
addrlen:客户端地址长度。
返回
返回一个已连接的客户端套接字描述符,失败返回-1,并设置errno
5.数据收发
#include <unistd.h>
//函数均返回读或写的字节个数,出错则返回-1。
ssize_t read(int sockfd, void *buf, size_t count);
ssize_t write(int sockfd, const void *buf,size_t count);
第二套
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//包含3要素:套接字s,待发数据msg,数据长度len
//函数只能对处于连接状态的套接字使用,参数s为已建立好连接的套接字描述//参数buf指向存放待发送数据的缓存区
//参数len为待发送数据的长度,参数flags为控制选项,一般设置为0
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//包含3要素:套接字s,待发数据buf,数据长度len
//函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收
//数据并保存到参数buf所指定的缓冲区
//参数len则为缓冲区长度,参数flags为控制选项,一般设置为0
6.客户端connect函数
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:该函数用于绑定之后的client客户端,与服务器建立连接。
参数
sockfd:目的服务器的socket描述符
addr:服务器端的IP地址和端口号的地址结构指针
addrlen:地址长度常被设置为sizeof(struct sockaddr)
返回
成功返回0,失败返回-1,并设置errno
7.关闭套接字
#include <unistd.h>
int close(int sockfd)
返回
成功返回0,失败返回-1,并设置errno
8.字节序转换
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//返回网络字节序的值
uint16_t htons(uint16_t hostshort);//返回网络字节序的值
uint32_t ntohl(uint32_t netlong);//返回主机字节序的值
uint16_t ntohs(uint16_t netshort);//返回主机字节序的值
h代表host,n代表net,s代表short(两个字节),l代表long(四个字节),通过上面四个函数来实现主机字节序与网络字节序之间的转换。
六、实现多消息发送
服务器代码:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
// int socket(int domain, int type, int protocol);
//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int main(int argc,char **argv)
{
int s_fd;
int c_fd;
int mark=0;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char readBuf[128];
// char *mes="I get your message";
char mes[128]={0};
int nread;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){
printf("paran is not good");
exit(-1);
}
//1.socket
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(-1);
}
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
//2.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
int clen = sizeof(struct sockaddr_in);
while(1){
//4.accept
c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
mark++;
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
if(fork()==0){
if(fork()==0){
while(1){
sprintf(mes,"welcom No %d client",mark);
write(c_fd,mes,strlen(mes));
sleep(3);
}
}
//5.read
while(1){
memset(readBuf,0,sizeof(readBuf));
nread=read(c_fd,readBuf,128);
if(nread == -1){
perror("read");
}else{
printf("get message:%d,%s\n",nread,readBuf);
}
}
break;
}
}
return 0;
}
客户端代码
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
// int socket(int domain, int type, int protocol);
//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int main(int argc,char **argv)
{
int c_fd;
struct sockaddr_in c_addr;
char readBuf[128];
// char *mes="mes from clint";
char mes[128]={0};
int nread;
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){
printf("paran is not good");
exit(-1);
}
//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
exit(-1);
}
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
//2.connet
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))==-1)
{
perror("connect");
exit(-1);
}
while(1){
//3.write
if(fork()==0){
while(1){
memset(mes,0,sizeof(mes));
printf("input: ");
gets(mes);
write(c_fd,mes,strlen(mes));
}
}
//4.read
while(1){
memset(readBuf,0,sizeof(readBuf));
nread=read(c_fd,readBuf,128);
if(nread == -1){
perror("read");
}else{
printf("get message from server:%d,%s\n",nread,readBuf);
}
}
}
return 0;
}
服务器:
客户端1
客户端2
学习笔记,仅供参考