Socket网络编程基础理解(TCP/UDP)
1、什么是套接字:
套接字是网络数据传输用的软件设备;
2、TCP
tcp是基于连接的通信方式。
2.1 服务端
2.1.1 调用socket函数生成“套接字”(相当于安装电话)
int socket(int domain, int type, int protocol);
domain:套接字中的协议族(protocol family)。
type:套接字数据传输类型信息。
protocol:计算机间通信中使用的协议信息。
返回值:成功时返回文件描述符,失败时返回-1;
2.1.2调用bind函数绑定ip和端口号(相当于给电话分配电话号码)
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
sockfd:将要分配地址信息的套接字文件描述符。
myaddr:存有地址信息的结构体变量地址值。
addrlen:第二个变量的长度。
返回值:成功时返回0,失败时返回-1;
2.1.3调用listen函数把套接字转化成可接收的状态(相当于连接电话线)
int listen(int sockfd, int backlog);
sockfd:希望进入等待连接请求状态的套接字描述符,传递的描述符套接字成为服务器端套接字。
backlog:队列长度。
返回值:成功时返回0,失败时返回-1;
2.1.4接受对方的连接请求(相当于可以拿起电话接电话了)
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
sockfd:服务器文件描述符。
addr:客户端地址信息的变量地址。
addrlen:存有第二个变量地址长度的变量地址。
返回值:成功时返回文件描述符,失败时返回-1;
2.2 TCP客户端
2.2.1 调用socket函数生成“套接字”(相当于安装电话)
int socket(int domain, int type, int protocol);
domain:套接字中的协议族(protocol family)。
type:套接字数据传输类型信息。
protocol:计算机间通信中使用的协议信息。
返回值:成功时返回文件描述符,失败时返回-1;
2.2.2 调用connect函数向服务器发送连接请求;
int connect(int sockfd, struct sockaddr* server_addr, socklen_t addrlen);
sockfd:客户端套接字文件描述符。
server_addr:服务端地址信息的变量地址。
addrlen:存有第二个变量地址长度的变量地址。
返回值:成功时返回0,失败时返回-1;
3、UDP
udp不同与tcp,udp是基于非连接的,udp不存在请求连接和接受连接过程,因此在这个意义上来讲udp无法明确的区分服务器和客户端,只是因为其提供服务而称为服务端。
一般情况来讲,tcp的可靠性高于udp,udp的传输速度快于tcp,但这并不绝对。
3.1 UDP相关函数
3.1.1 创建套接字
和TCP相同。
3.1.2 发送信息端函数
ssize_t sendto(int sockfd, void* buff, size_t nbytes, int flags, struct sockaddr* to, socklen_t addrlen);
sockfd:用于发送的套接字描述。
buff:保存待传输数据的缓冲地址;
nbytes:待传输的数据长度,以字节为单位。
flags:可选项参数,若没有则传递0。
to:目标地址的地址值。
addrlen:地址值结构体变量长度。
返回值:成功时返回传输的字节数,失败时返回-1。
ssize_t recvfrom(int sockfd, void* buff, size_t nbytes, int flags, struct sockaddr* from, socklen_t addrlen);
sockfd:用于接收的套接字描述。
buff:保存接收数据的缓冲地址;
nbytes:数据长度,以字节为单位。
flags:可选项参数,若没有则传递0。
from:发送端地址信息的地址值。
addrlen:发送端地址值结构体变量长度的变量地址值。
返回值:成功时返回传输的字节数,失败时返回-1。
4、Linux案例
4.1 TCP
TCP_server.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int server_sockfd; //服务器端套接字
int client_sockfd; //客户端套接字
int len;
struct sockaddr_in my_addr; //服务器网络地址结构体
struct sockaddr_in remote_addr; //客户端网络地址结构体
int sin_size;
char buf[BUFSIZ]; //数据传送的缓冲区
memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
my_addr.sin_family=AF_INET; //设置为IP通信
my_addr.sin_addr.s_addr=INADDR_ANY; //服务器IP地址--允许连接到所有本地地址上
my_addr.sin_port=htons(7088); //服务器端口号
/*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/
//socket返回的值是一个文件描述符,SOCKET类型本身也是定义为int的
//既然是文件描述符,那么在系统中都当作是文件来对待的,0,1,2分别表示标准输入、标准输出、标准错误。
//所以其他打开的文件描述符都会大于2, 错误时就返回 -1. 这里INVALID_SOCKET 也被定义为 -1
if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) // 无错误发生,socket()返回引用新套接口的描述字。否则的话,返回INVALID_SOCKET错误(-1)!
{
perror("socket创建失败!");
return 1;
}
/*将套接字绑定到服务器的网络地址上*/
if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) //如无错误发生,则bind()返回0。否则的话,将返回-1
{
perror("bind失败!");
return 1;
}
/*监听连接请求--监听队列长度为5*/
listen(server_sockfd, 5); //如无错误发生,则listen()返回0。否则的话,将返回-1
sin_size=sizeof(struct sockaddr_in);
/*等待客户端连接请求到达*/
if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr, &sin_size))<0) //如无错误发生,则accept()返回0。否则的话,将返回-1
{
perror("accept错误!");
return 1;
}
printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
printf("成功建立连接!\n");
len=send(client_sockfd, "welcome!\n", 22, 0);//发送欢迎信息
/*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
while((len=recv(client_sockfd,buf,BUFSIZ,0))>0)
{
buf[len]='\0';
printf("%s\n",buf);
if(send(client_sockfd,buf,len,0)<0)
{
perror("write");
return 1;
}
}
close(client_sockfd);
close(server_sockfd);
return 0;
}
TCP_client.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
int main(int argc, char *argv[])
{
int client_sockfd;
int len;
struct sockaddr_in remote_addr; //服务器端网络地址结构体
char buf[BUFSIZ]; //数据传送的缓冲区
memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零
remote_addr.sin_family=AF_INET; //设置为IP通信
remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1"); //服务器IP地址
remote_addr.sin_port=htons(7088); //服务器端口号
/*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
{
perror("socket创建失败!");
return 1;
}
/*将套接字绑定到服务器的网络地址上*/
if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)
{
perror("连接失败!");
return 1;
}
printf("connected to server\n");
len=recv(client_sockfd, buf, BUFSIZ, 0); //接收服务器端信息 , recv函数第一个参数指定接收端套接字描述符;返回其实际copy的字节数。
buf[len]='\0';
printf("from server: %s",buf); //打印服务器端信息
/*循环的发送接收信息并打印接收信息--recv返回接收到的字节数,send返回发送的字节数*/
while(1)
{
printf("Client enter string to send:");
scanf("%s", buf);
if(!strcmp(buf,"quit")){ // strcmp() 函数用于对两个字符串进行比较(区分大小写),如果返回值为0,说明比较的两个字符相等
break;
}
len=send(client_sockfd,buf,strlen(buf),0); //如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。
len=recv(client_sockfd,buf,BUFSIZ,0);
buf[len]='\0';
printf("Server:%s\n",buf);
}
close(client_sockfd); //关闭套接字
return 0;
}
4.2、UDP
UDP_server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char *argv[])
{
int server_sockfd;
int len;
struct sockaddr_in my_addr; //服务器网络地址结构体
struct sockaddr_in remote_addr; //客户端网络地址结构体
int sin_size;
char buf[BUFSIZ]; //数据传送的缓冲区
memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
my_addr.sin_family=AF_INET; //设置为IP通信
my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
my_addr.sin_port=htons(8000); //服务器端口号
/*创建服务器端套接字--IPv4协议,面向无连接通信,UDP协议*/
if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0)
{
perror("socket");
return 1;
}
/*将套接字绑定到服务器的网络地址上*/
if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
{
perror("bind");
return 1;
}
sin_size=sizeof(struct sockaddr_in);
printf("waiting for a packet...\n");
/*接收客户端的数据并将其发送给客户端--recvfrom是无连接的*/
if((len=recvfrom(server_sockfd,buf,BUFSIZ,0,(struct sockaddr *)&remote_addr,&sin_size))<0)
{
perror("recvfrom");
return 1;
}
printf("received packet from %s:\n",inet_ntoa(remote_addr.sin_addr));
buf[len]='\0';
printf("contents: %s\n",buf);
close(server_sockfd);
return 0;
}
UDP_client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char *argv[])
{
int client_sockfd;
int len;
struct sockaddr_in remote_addr; //服务器端网络地址结构体
int sin_size;
char buf[BUFSIZ]; //数据传送的缓冲区
memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零
remote_addr.sin_family=AF_INET; //设置为IP通信
remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址
remote_addr.sin_port=htons(8000); //服务器端口号
/*创建客户端套接字--IPv4协议,面向无连接通信,UDP协议*/
client_sockfd=socket(PF_INET,SOCK_DGRAM,0);
if(client_sockfd<0)
{
perror("socket");
return 1;
}
strcpy(buf,"This is a test message");
printf("sending: '%s'\n",buf);
sin_size=sizeof(struct sockaddr_in);
/*向服务器发送数据包*/
len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr));
if(len < 0)
{
perror("recvfrom");
return 1;
}
close(client_sockfd);
return 0;
}