前言:socket网络编程是一门技术,它主要是在网络通信中经常用到。与进程间通信最大的区别是socket网络编程是基于多机信息传递的,而前者基于单机。
一、了解TCP和UDP的区别。
①TCP面向连接(如打电话要先拨号建立连接;UDP是无连接的,即发送数据之前不需要建立连接。
②TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
③TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
④每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
⑤TCP首部开销20字节;UDP的首部开销小,只有8个字节。
⑥TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
二、socket编程使用的API
1.字节序转换API
uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue); //返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue); //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值
h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
2.地址转换API
int inet_aton(const char * straddr,struct in_addr *addrp)
说明:把字符串形式的"192.168.1.123"转换为网络能识别的格式
第一个参数:IP地址的字符串。
第二个参数:字符串的地址
char *inet_ntoa(struct in_addr inaddr);
把网络格式的ip地址转为字符串形式
3.创建通信端点 socket
int socket(int domain, int type, int protocol);
第一个参数为:指定一个通信域,通常使用AF_INET(IPv4 Internet protocols)
第二个参数为:socket的制定类型,通常使用SOCK_STREAM(提供有序的、可靠的、双向的、基于连接的字节流。可以采用带外数据传输机制支持。)
第三个参数为:参数protocol用来指明所要接收的协议包,通常写0用于接收任何的IP数据包。其中的校验和和协议分析由程序自己完成
返回值:如果成功,将返回新套接字的文件描述符。若失败则返回-1。
eg: socket(AF_INET,SOCK_STREAM,0);
4.将名称绑定到套接字 bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
第一个参数为:创建通信端点时返回的文件标识符。
第二个参数为:addr为结构体变量的指针,这里我们使用 sockaddr_in 结构体,然后再强制转换为 sockaddr 类型。
sockaddr_in 结构体
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
uint16_t sin_port; //16位的端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
第三个参数为:addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
返回值:如果成功,则返回0。出现错误时,返回-1,并设置errno适当。
eg:bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
5.监听套接字上的连接 listen
int listen(int sockfd, int backlog);
第一个参数为:创建通信端点时返回的文件标识符。
第二个参数为:backlog参数定义了待定队列的最大长度sockfd的连接可能会增加。如果连接请求到达时如果队列已满,客户端可能会收到一个指示为ECONNRE‐的错误融合或,如果底层协议支持重传,请求可以被忽略,以便以后重新尝试连接时成功。
返回值:如果成功,则返回0。出现错误时,返回-1,并设置errno适当。
eg:listen(s_fd,10);
6.接收套接字上的连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
第一个参数为:创建通信端点时返回的文件标识符。
第二个参数为:参数addr是指向sockaddr结构体的指针,需要转换类型。若为NULL,则addrlen也为空
第三个参数为:adr长度的地址.
返回值:如果成功,这些系统调用将返回一个非负整数a被接受套接字的描述符。错误时,返回-1,errn为适当的设置。
eg: int adr=sizeof(struct sockaddr_in);
a_fd=accept(s_fd,(struct sockaddr *)&a_addr,&adr);
7.在套接字上发起连接 connect
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第一个参数为:创建通信端点时返回的文件标识符。
第二个参数为:addr为要访问的地址。
第三个参数为:要访问地址的长度。
返回值:如果成功,则返回0。出现错误时,返回-1,并设置errno适当。
三、服务器端的构建(双人)
①创建通信端点套接字
②将名称绑定到套接字(IP地址和端口号)
③监听连接
④接收套接字的连接
⑤数据交互(读取、发送)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int s_fd;
int a_fd;
int n_read;
char readbuff[128];
char msg[128]={0};
struct sockaddr_in s_addr;
struct sockaddr_in a_addr;
if(argc!=3){
printf("progrm is not good\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in)); //reset
memset(&a_addr,0,sizeof(struct sockaddr_in)); //reset
//1.socket
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
//2.bind
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));
//s_addr.sin_addr.s_addr=("127.0.0.1");
inet_aton(argv[1],&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int adr=sizeof(struct sockaddr_in);
while(1){
a_fd=accept(s_fd,(struct sockaddr *)&a_addr,&adr);
if(a_fd==-1){
perror("accept");
}
printf("get connect:%s\n",inet_ntoa(a_addr.sin_addr));
if(fork()==0){
if(fork()==0){
while(1){
//write
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(a_fd,msg,strlen(msg));
}
}
//read
while(1){
memset(readbuff,0,sizeof(readbuff));
n_read=read(a_fd,readbuff,128);
if(n_read==-1){
perror("read");
}else{
printf("get message from client:%d,%s\n",n_read,readbuff);
}
}
break;
}
}
return 0;
}
四、接收端的构建(双人)
①创建通信端点
②发起连接
③发送数据
④读取数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int c_fd;
int n_read;
char readbuff[128];
char msg[128]={0};
struct sockaddr_in c_addr;
if(argc!=3){
printf("progrm is not good\n");
exit(-1);
}
memset(&c_addr,0,sizeof(struct sockaddr_in)); //reset
//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket:\n");
exit(-1);
}
//2.connect
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))==-1){
perror("connect\n");
exit(-1);
}
while(1){
if(fork()==0){
while(1){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
//3.write
write(c_fd,msg,strlen(msg));
}
}
while(1){
//3.read
memset(readbuff,0,sizeof(readbuff));
n_read=read(c_fd,readbuff,128);
if(n_read==-1){
printf("perror:\n");
}else{
printf("I get message from server:%d,context:%s\n",n_read,readbuff);
}
}
}
return 0;
}
进行多机通讯时只需要修改服务器端
①定义mark来标识用户端号数
②更改accept
//4.accept
int adr=sizeof(struct sockaddr_in);
while(1){
a_fd=accept(s_fd,(struct sockaddr *)&a_addr,&adr);
if(a_fd==-1){
perror("accept");
}
mark++;
printf("get connect:%s\n",inet_ntoa(a_addr.sin_addr));
if(fork()==0){
if(fork()==0){
while(1){
//write
sprintf(msg,"Welcom No.%d client",mark); //当前用户端号数
write(a_fd,msg,strlen(msg));
sleep(3);
}
}
//read
while(1){
memset(readbuff,0,sizeof(readbuff));
n_read=read(a_fd,readbuff,128);
if(n_read==-1){
perror("read");
}else{
printf("get message from client:%d,%s\n",n_read,readbuff);
}
}
break;
}
}