在我们编写程序前,我们需要先了解TCP协议的特点。
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
在了解了TCP协议的特点后,我们就来实现一个简单的TCP网络程序。
在实现过程中,我们需要用到最重要的东西就是socket套接字。
socket:网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端就称为一个socket。socket本质是编程接 口,建立网络通信连接至少要一对端口号,在网络中,我们用IP加端口号来表示互联网中唯一一个进程,所以socket就是IP地址加上端口号。
创建
函数原型:int socket(int domain,int type,int protocol);
domain:协议域,又称协议族。常用的协议族有AF_INET、AF_INET6、AF_LOCAL等。协议族决定了socket的地址类型,在通信中必须采用对应的地址。
type:指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM等。流式socket(SOCK_STREAM)是一种面向连 接的socket,针对于面向连接的TCP服务,数据报式socket(SOCK_DGRAM)是一种无连接的socket,对应于无连接的UDP服务。
protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP等,分别对应TCP传输协议,UDP传输协议和 STCP传输协议。
返回值:如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALI_SOCKET(Linux下失败返回-1)。
绑定
函数原型:int bind(SOCKET socket,const struct sockaddr* address,socklen_t address_len);
socket:是一个套接字描述符。
address:是一个sockaddr结构指针,该结构中包含了要结合的地址和端口号。
address_len:确定address缓冲区的长度。
返回值:成功返回0,否则返回SOCKET_ERROR。
接收
函数原型:int recvfrom(int socket,void buf,int len,unsigned int flags,struct socketaddr* from,socket_t* fromlen);
socket:标识一个已连接套接口的描述字。
buf:用于接收数据的缓冲区。
len:缓冲区长度。
flags:调用操作方式。一般设置为0。
from:指向装有源地址的缓冲区。
fromlen:指向from缓冲区的长度。
返回值:若无错误发生,返回读入的字节数。失败返回-1。
发送
函数原型:int sendto(SOCKET socket,const char FAR* buf,int size,int flags,const struct sockaddr FAR* to,int tolen);
socket:套接字。
buf:待发送数据的缓冲区。
size:缓冲区长度。
flags:调用方式标志位,一般为0,改变flags,将会改变sendto发送的形式。
addr:指向目的套接字的地址。
tolen:addr所指地址的长度。
返回值:如果成功,则返回发送的字节数,失败则返回-1。
接收连接请求
int accept(int fd,struct socketaddr* addr,socklen_t* len);
fd:套接字描述符。
addr:返回连接着的地址。
len:接收返回地址的缓冲区长度。
返回值:成功返回客户端的文件描述符,失败返回-1。
在了解了所需要用的函数后,我们就来具体实现它。
//client(客户端)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char* argv[])
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
perror("sock");
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(atoi(argv[2]));
server.sin_addr.s_addr=inet_addr(argv[1]);
int ret = connect(sock,(struct sockaddr*)&server,sizeof(server));
if(ret<0)
perror("connect");
while(1)
{
char buf[1024];
buf[0];
printf("client : # ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf));
if(s>0)
{
buf[s-1]=0;
}
write(sock,buf,sizeof(buf));
if(strncmp(buf,"quit",4)==0)
break;
read(sock,buf,sizeof(buf));
printf("server : $ %s\n",buf);
}
close(sock);
return 0;
}
//server(服务器端)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char* argv[])
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
perror("socket");
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(atoi(argv[2]));
local.sin_addr.s_addr=inet_addr(argv[1]);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
perror("bind");
if(listen(sock,5)<0)
perror("listen");
while(1)
{
char buf[1024];
buf[0]=0;
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newsock = accept(sock,(struct sockaddr*)&peer,&len);
if(newsock<0)
perror("accept");
inet_ntop(AF_INET,&peer.sin_addr,buf,sizeof(buf));
printf("get a connect,ip:%s,port:%d\n",buf,ntohs(peer.sin_port));
while(1)
{
ssize_t s=read(newsock,buf,sizeof(buf));
if(s>0)
{
buf[s]=0;
printf("[%s:%d] %s\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
}
else
{
printf("client quit\n");
break;
}
write(newsock,buf,strlen(buf)+1);
}
}
close(sock);
return 0;
}
接下来我们来验证程序是否可以成功运行,我们打开两个终端。运行输入相同IP和端口号来测验。
由图我们能看到,两个终端确实连接成功了,我们使用客户端输入信息来测试。
服务器端可以收到客户端发送的信息,也可以对其内容进行回应,这就是一个简单的TCP网络程序,只要在相同局域网下的用户都可以进行连接。